为什么通过装饰器和 class 主体进行的 class.__slots__ 赋值存在差异?
Why differences in class.__slots__ assignment via decorator vs. class body?
我正在开发一个装饰器来为不可变 class 实现一些行为。我想要一个 class 从 namedtuple 继承(具有属性不变性)并且还想添加一些新方法。 Like this ...但正确地阻止了将新属性分配给新的 class.
继承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
...太棒了。但现在我想将 __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)
这有点令人惊讶。
为什么将 __slots__
的赋值移动到装饰器中会导致行为发生变化?
如果我打印 C.__slots__
,我得到我分配的对象。 x 存储了什么?
代码无效,因为 __slots__
不是在 运行 时咨询的正常 class 属性。它是 class 的基础 属性,影响其每个实例的内存布局,因此必须在创建 class 时知道并在其整个生命周期中保持静态。虽然 Python(可能是为了向后兼容)允许稍后分配给 __slots__
,但分配对现有或未来实例的行为没有影响。
如何设置 __slots__
class作者确定的__slots__
值在创建class对象时传递给class构造函数。这是在执行 class
语句时完成的;例如:
class X:
__slots__ = ()
上面的语句等同于1创建一个class对象并将其赋值给X
:
X = type('X', (), {'__slots__': ()})
type
对象是 metaclass,在调用时创建和 returns 一个 class 的工厂。 metaclass 调用接受类型的名称、它的超classes 和它的定义字典。定义字典的大部分内容也可以稍后赋值定义字典包含影响class实例低级布局的指令。正如您所发现的,稍后分配给 __slots__
根本没有效果。
从外部设置__slots__
要修改 __slots__
以便它被 Python 拾取,必须在创建 class 时指定它。这可以通过 metaclass 来完成,该类型负责实例化类型。 metaclass 驱动 class 对象的创建,它可以确保 __slots__
在调用构造函数之前进入 class 定义字典:
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"
测试结果符合预期:
>>> 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'
Metaclass 混合陷阱
请注意,C
类型对象本身将是 DefineMeta
的 实例 - 这并不奇怪,因为这是从 a 的定义得出的元class。但是,如果您从 C
和指定元 class 而不是 type
或 DefineMeta
的类型继承,这可能会导致错误。由于我们只需要 metaclass 挂钩到 class 的创建中,但以后不会使用它,因此 C
并不是必须作为 [=28= 的实例创建的] - 我们可以改为使它成为 type
的实例,就像任何其他 class 一样。这是通过更改行来实现的:
defcls = super().__new__(metacls, name, bases, dct)
至:
defcls = type.__new__(type, name, bases, dct)
__new__
和__slots__
的注入将保留,但C
将是最普通的类型,默认metaclass。
总之...
定义一个简单地调用 superclass __new__
的 __new__
总是多余的——大概真正的代码也会在注入的 __new__
中做一些不同的事情,例如为命名元组提供默认值。
1
在实际的 class 定义中,编译器向 class 字典添加了一些额外的项目,例如模块名称。这些很有用,但它们不会像 __slots__
那样从根本上影响 class 定义。如果 X
有方法,它们的函数对象也将包含在由函数名称键入的字典中——作为在 class 定义命名空间字典中执行 def
语句的副作用自动插入。
__slots__
必须在 class 创建期间存在。它会影响 class 实例的内存布局,这不是您可以随意更改的内容。 (想象一下,如果您已经拥有 class 的实例,并且您试图在此时重新分配 class 的 __slots__
;实例将全部中断。)基于内存布局的处理__slots__
仅在 class 创建期间发生。
在装饰器中分配 __slots__
已经来不及做任何事情了。它必须在 class 创建之前发生,在 class 正文或元 class __new__
.
中
此外,您的define_new
毫无意义; namedtuple.__new__
已经做了您需要 __new__
做的事情。
我正在开发一个装饰器来为不可变 class 实现一些行为。我想要一个 class 从 namedtuple 继承(具有属性不变性)并且还想添加一些新方法。 Like this ...但正确地阻止了将新属性分配给新的 class.
继承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
...太棒了。但现在我想将 __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)
这有点令人惊讶。
为什么将 __slots__
的赋值移动到装饰器中会导致行为发生变化?
如果我打印 C.__slots__
,我得到我分配的对象。 x 存储了什么?
代码无效,因为 __slots__
不是在 运行 时咨询的正常 class 属性。它是 class 的基础 属性,影响其每个实例的内存布局,因此必须在创建 class 时知道并在其整个生命周期中保持静态。虽然 Python(可能是为了向后兼容)允许稍后分配给 __slots__
,但分配对现有或未来实例的行为没有影响。
如何设置 __slots__
class作者确定的__slots__
值在创建class对象时传递给class构造函数。这是在执行 class
语句时完成的;例如:
class X:
__slots__ = ()
上面的语句等同于1创建一个class对象并将其赋值给X
:
X = type('X', (), {'__slots__': ()})
type
对象是 metaclass,在调用时创建和 returns 一个 class 的工厂。 metaclass 调用接受类型的名称、它的超classes 和它的定义字典。定义字典的大部分内容也可以稍后赋值定义字典包含影响class实例低级布局的指令。正如您所发现的,稍后分配给 __slots__
根本没有效果。
从外部设置__slots__
要修改 __slots__
以便它被 Python 拾取,必须在创建 class 时指定它。这可以通过 metaclass 来完成,该类型负责实例化类型。 metaclass 驱动 class 对象的创建,它可以确保 __slots__
在调用构造函数之前进入 class 定义字典:
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"
测试结果符合预期:
>>> 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'
Metaclass 混合陷阱
请注意,C
类型对象本身将是 DefineMeta
的 实例 - 这并不奇怪,因为这是从 a 的定义得出的元class。但是,如果您从 C
和指定元 class 而不是 type
或 DefineMeta
的类型继承,这可能会导致错误。由于我们只需要 metaclass 挂钩到 class 的创建中,但以后不会使用它,因此 C
并不是必须作为 [=28= 的实例创建的] - 我们可以改为使它成为 type
的实例,就像任何其他 class 一样。这是通过更改行来实现的:
defcls = super().__new__(metacls, name, bases, dct)
至:
defcls = type.__new__(type, name, bases, dct)
__new__
和__slots__
的注入将保留,但C
将是最普通的类型,默认metaclass。
总之...
定义一个简单地调用 superclass __new__
的 __new__
总是多余的——大概真正的代码也会在注入的 __new__
中做一些不同的事情,例如为命名元组提供默认值。
1 在实际的 class 定义中,编译器向 class 字典添加了一些额外的项目,例如模块名称。这些很有用,但它们不会像
__slots__
那样从根本上影响 class 定义。如果 X
有方法,它们的函数对象也将包含在由函数名称键入的字典中——作为在 class 定义命名空间字典中执行 def
语句的副作用自动插入。
__slots__
必须在 class 创建期间存在。它会影响 class 实例的内存布局,这不是您可以随意更改的内容。 (想象一下,如果您已经拥有 class 的实例,并且您试图在此时重新分配 class 的 __slots__
;实例将全部中断。)基于内存布局的处理__slots__
仅在 class 创建期间发生。
在装饰器中分配 __slots__
已经来不及做任何事情了。它必须在 class 创建之前发生,在 class 正文或元 class __new__
.
此外,您的define_new
毫无意义; namedtuple.__new__
已经做了您需要 __new__
做的事情。