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
和指定了除type
or 以外的元类的类型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 次 |
最近记录: |