实例是否可以在不使用__init__的情况下获得自己的类级别可变默认值的副本?

时间:2012-03-09 18:13:14

标签: class python-3.x default-value mutable metaclass

我正在编写一个元类来做一些很酷的事情,它的部分处理是在创建类时检查某些属性是否存在。其中一些是可变的,通常会在__init__中设置,但由于__init__在创建实例之前未运行,因此元类将不知道属性被创建,并引发错误。我可以做类似的事情:

class Test(meta=Meta):
    mutable = None
    def __init__(self):
        self.mutable = list()

但这种方法有几个问题:

  • 它强制创建与实例属性
  • 类型不同的类属性
  • __init__仍然需要隐藏class属性,而Meta不会检查
  • 如果__init__没有影响类属性,我仍会得到错误(例如AttributeError: 'NoneType' object has no attribute 'append'),并且试图避免此类错误是{{1的函数的一部分}}
  • 最后但并非最不重要的是,它违反了DRY。

我需要的是一种类似的方式:

Meta

但每个实例最终都会有自己的class Test(metaclass=Meta): mutable = list() 副本。

设计目标是:

  • 该属性必须存在于类中(类型无关紧要 - 未检查)
  • 在首次使用该属性之前的某个时刻,该属性的副本将放置在实例

所需的样本运行:

mutable

关于如何实现这一目标的任何想法?

1 个答案:

答案 0 :(得分:1)

至少有三种方法可以做到这一点:

  1. 让元类检查所有属性,如果它们是可变项之一(listdictset等,则将属性替换为描述符这将在第一次访问时激活,并使用可变的新副本更新实例。

  2. 提供(1)中的描述符作为编写类时使用的装饰器。

  3. 让元类将自己的__init__方法添加到运行时的类中:
    • 调用原始__init__
    • 然后检查是否存在所需的属性
  4. 下行(按方法):

    1. 如果该类具有在所有实例之间共享的可变属性,则需要额外的工作。

    2. 类中的属性成为类中的一个函数(可能是mind-warp;)

    3. 将错误点移至类实例化而不是类定义。
    4. 我更喜欢(2)它是否为类作者提供了完全控制,简化了在所有实例之间共享类级别可变属性的情况,并将错误保留在类定义中

      这是装饰者描述符:

      class ReplaceMutable:
          def __init__(self, func):
              self.func = func
          def __call__(self):
              return self
          def __get__(self, instance, owner):
              if instance is None:
                  return self
              result = self.func()
              setattr(instance, self.func.__name__, result)
              return result
      

      和测试类:

      class Test:
          @ReplaceMutable
          def mutable():
              return list()
      

      工作原理:

      就像property一样,ReplaceMutable是一个描述符对象,其名称与它所替换的属性相同。与property不同,它不定义__set__也不定义__delete__,因此当代码尝试重新绑定实例中的名称(上面的测试中为mutable)时,Python将允许它这样做。这与缓存描述符背后的想法相同。

      ReplaceMutable正在装饰一个函数(带有所需属性的名称),它只返回应初始化实例级别属性的任何内容(上例中的空list)。因此,第一次在实例上查找属性时,它将无法在实例字典中找到,Python将激活描述符;然后,描述符调用函数来检索初始对象/数据/任何内容,将其存储在实例中,然后返回它。下次在该实例上访问该属性时,它将在实例字典中,这就是将要使用的内容。

      示例代码:

      t1 = Test()
      t2 = Test()
      t1.mutable.append('one')
      t2.mutable.append('two')
      print(t1.mutable)
      print(t2.mutable)