use*_*956 0 python decorator class-method namedtuple
我正在一个装饰器上实现不可变类的某些行为。我想要一个从namedtuple继承的类(具有属性不变性),并且还想添加一些新方法。像这样 ...但是正确防止将新属性分配给新类。
从namedtuple继承时,应定义__new__并设置__slots__为空元组(以保持不变性):
def define_new(clz):
def __new(cls, *args, **kwargs):
return super(clz, cls).__new__(cls, *args, **kwargs)
clz.__new__ = staticmethod(__new) # delegate namedtuple.__new__ to namedtuple
return clz
@define_new
class C(namedtuple('Foo', "a b c")):
__slots__ = () # Prevent assignment of new vars
def foo(self): return "foo"
C(1,2,3).x = 123 # Fails, correctly
Run Code Online (Sandbox Code Playgroud)
太好了 但是现在我想将__slots__任务移到装饰器中:
def define_new(clz):
def __new(cls, *args, **kwargs):
return super(clz, cls).__new__(cls, *args, **kwargs)
#clz.__slots__ = ()
clz.__slots__ = (123) # just for testing
clz.__new__ = staticmethod(__new)
return clz
@define_new
class C(namedtuple('Foo', "a b c")):
def foo(self): return "foo"
c = C(1,2,3)
print c.__slots__ # Is the (123) I assigned!
c.x = 456 # Assignment succeeds! Not immutable.
print c.__slots__ # Is still (123)
Run Code Online (Sandbox Code Playgroud)
这有点令人惊讶。
为什么将的分配__slots__移入装饰器会导致行为发生变化?
如果我打印C.__slots__,则会得到分配的对象。x存储什么?
该代码不起作用,因为__slots__在运行时未咨询普通的类属性。它是类的基本属性,会影响其每个实例的内存布局,因此在创建类时必须知道该类,并且在其整个生命周期内都保持静态。尽管Python(大概是为了向后兼容)允许__slots__以后分配,但分配对现有实例或将来实例的行为没有影响。
__slots__设置__slots__创建类对象时,由类作者确定的值将传递给类构造函数。这是在class执行语句时完成的;例如:
class X:
__slots__ = ()
Run Code Online (Sandbox Code Playgroud)
上面的语句等效于1,它创建一个类对象并将其分配给X:
X = type('X', (), {'__slots__': ()})
Run Code Online (Sandbox Code Playgroud)
该type对象是元类,创建并调用时返回类工厂。元类调用接受类型的名称,其超类及其定义dict。定义dict的大多数内容也可以在以后分配。定义dict包含影响类实例的底层布局的指令。如您所知,以后分配给__slots__根本没有效果。
__slots__从外面设置要进行修改__slots__以便被Python拾取,必须在创建类时指定它。这可以通过metaclass(负责实例化类型的类型)来完成。元类驱动类对象的创建,并且可以确保__slots__在调用构造函数之前将其放入类定义dict中:
class DefineNew(type):
def __new__(metacls, name, bases, dct):
def __new__(cls, *new_args, **new_kwargs):
return super(defcls, cls).__new__(cls, *new_args, **new_kwargs)
dct['__slots__'] = ()
dct['__new__'] = __new__
defcls = super().__new__(metacls, name, bases, dct)
return defcls
class C(namedtuple('Foo', "a b c"), metaclass=DefineNew):
def foo(self):
return "foo"
Run Code Online (Sandbox Code Playgroud)
测试结果符合预期:
>>> c = C(1, 2, 3)
>>> c.foo()
'foo'
>>> c.bar = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'bar'
Run Code Online (Sandbox Code Playgroud)
请注意,C类型对象本身将是- 的实例,DefineMeta这并不奇怪,因为它来自元类的定义。但这可能会导致错误,如果您同时继承两者C和指定了除typeor 以外的元类的类型DefineMeta。由于我们只需要将元类挂接到类的创建上,而以后不再使用它,则不必严格C将其创建为的实例DefineMeta-我们可以type像其他任何类一样将其创建为的实例。这是通过更改以下行来实现的:
defcls = super().__new__(metacls, name, bases, dct)
Run Code Online (Sandbox Code Playgroud)
至:
defcls = type.__new__(type, name, bases, dct)
Run Code Online (Sandbox Code Playgroud)
的注入__new__,并__slots__会保留,但C将是一个最普通的类型使用默认元类。
定义一个__new__简单地调用超类__new__的a总是多余的-假定实际代码还会在注入中做一些不同的事情__new__,例如,为namedtuple提供默认值。
__slots__。如果X有方法,则它们的函数对象也将包含在以函数名作为键的dict中-作为def在类定义名称空间dict 中执行该语句的副作用而自动插入。
| 归档时间: |
|
| 查看次数: |
159 次 |
| 最近记录: |