提问人:Wör Du Schnaffzig 提问时间:2/3/2021 最后编辑:Wör Du Schnaffzig 更新时间:2/3/2021 访问量:619
Python 元类定义__slots__使__slots__只读
Python Metaclass defining __slots__ makes __slots__ readonly
问:
在下面的示例中,我尝试创建一个 python 元类,该元类是我的类,具有默认值。__slots__
class Meta(type):
def __new__(cls, name, bases, dictionary, defaults):
dictionary['__slots__'] = list(defaults.keys())
obj = super().__new__(cls, name, bases, dictionary)
return obj
def __init__(self, name, bases, dictionary, defaults):
for s in defaults:
setattr(self, s, defaults[s])
class A(metaclass = Meta, defaults = {'a':123, 'b':987}):
pass
实例化类,我得到以下结果:A
a = A()
>>> dir (a)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'a', 'b']
-> OK
>>> a.c = 500
Traceback (most recent call last):
File "<pyshell#87>", line 1, in <module>
a.c = 500
AttributeError: 'A' object has no attribute 'c'
-> OK
>>> a.b = 40
Traceback (most recent call last):
File "<pyshell#88>", line 1, in <module>
a.b = 40
AttributeError: 'A' object attribute 'b' is read-only
-> 不行,预计可读可写a.b
A 您可以看到元类正确地创建并正确设置了默认值,但不幸的是,由于某种原因,我不明白,插槽属性被设置为只读。
是否可以从元类中获取槽读/写属性?Meta
__slots__
Meta
答:
问题是设置属性的代码在类本身中更改它。问题在于类中的默认变量(在本例中为“a”和“b”默认值)是特殊的描述符,用于处理所创建类(示例中的对象“a”)实例中的槽值赋值。描述符将被覆盖,无法再工作。
(它们成为“只读类属性”确实是一个特殊的副作用 - 我将调查这是否是记录的或故意的,或者只是一个未定义的行为)Meta.__init__
尽管如此,您需要的是一种设置方法,以便在实例化对象后使值在 slot-variables 中可用。
一个显而易见的方法是将 u 中的逻辑转移到基类方法中,并在那里设置值(将字典附加到类本身)。然后,调用的任何子类都将拥有它。Meta.__init__
__init__
defaults
super().__init__()
如果你不想,或者不能这样做,你可以把代码放在元类中,在每个类中注入一个,如果有的话,包装原始的(并处理所有可能的情况,比如:不,已经在父类中包装了,等等)——这可以做到,如果你选择这样做,我可以提供一些示例代码。__init__
__init__
__init__
__init__
(更新:再想一想,可以在元类方法上设置代码,并完全覆盖默认值,而不是所有这些错误,因此默认值赋值发生在调用类之前)__call__
type.__call__
__init__
class Meta(type):
def __new__(mcls, name, bases, dictionary, defaults):
dictionary['__slots__'] = list(defaults)
dictionary["_defaults"] = defaults
return super().__new__(mcls, name, bases, dictionary)
def __call__(cls, *args, **kw):
"""Replaces completly the mechanism that makes `__new__` and
`__init__` being called, adding a new step between the two calls
"""
instance = cls.__new__(cls, *args, **kw)
for k, v in instance._defaults.items():
setattr(instance, k, v)
instance.__init__(*args, **kw)
return instance
class A(metaclass = Meta, defaults = {'a':123, 'b':987}):
def __init__(self):
print (f"I can see the default values of a and b: {(self.a, self.b)}")
它起作用:
In [51]: A()
I can see the default values of a and b: (123, 987)
Out[51]: <__main__.A at 0x7f093cfeb820>
In [52]: a = A()
I can see the default values of a and b: (123, 987)
In [53]: a.c = 500
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-53-ce3d946a718e> in <module>
----> 1 a.c = 500
AttributeError: 'A' object has no attribute 'c'
In [54]: a.b
Out[54]: 987
In [55]: a.b = 1000
In [56]: a.b
Out[56]: 1000
另一种方法是创建知道默认值的特殊描述符。更改添加前缀(如“_”)的槽变量名称,并使用这些描述符来访问它们。这有点简单,虽然它比编写元类更复杂,但 ou 的优点是能够在描述符本身上放置额外的保护代码(例如:拒绝分配默认值类型不同的值)__call__
PREFIX = "_"
class DefaultDescriptor:
def __init__(self, name, default):
self.name = name
self.default = default
def __get__(self, instance, owner):
if instance is None:
return self
# or, if you want the default value to be visible as a class attribute:
# return self.default
return getattr(instance, PREFIX + self.name, self.default)
def __set__(self, instance, value):
setattr(instance, PREFIX + self.name, value)
class Meta(type):
def __new__(mcls, name, bases, dictionary, defaults):
dictionary['__slots__'] = [PREFIX + key for key in defaults]
cls = super().__new__(mcls, name, bases, dictionary)
for key, value in defaults.items():
setattr(cls, key, DefaultDescriptor(key, value))
return cls
class A(metaclass = Meta, defaults = {'a':123, 'b':987}):
pass
在 REPL 上:
In [37]: a = A()
In [38]: a.c = 500
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-38-ce3d946a718e> in <module>
----> 1 a.c = 500
AttributeError: 'A' object has no attribute 'c'
In [39]: a.b
Out[39]: 987
In [40]: a.b = 1000
In [41]: a.b
Out[41]: 1000
评论