在下面的示例中,我尝试创建一个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']->正常
>>> 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'->正常
>>> 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获取时隙读/写属性?
发布于 2021-02-03 14:19:18
问题是,在Meta.__init__中设置属性的代码更改了类本身中的属性。问题是类中的默认变量(在本例中为"a“和"b”默认值)是特殊的描述符,用于处理所创建类的实例中的槽值分配(示例中的对象"a“)。描述符被覆盖,不能再工作了。(它们成为“只读类属性”确实是一种特殊的副作用-我将调查这是有文档记录的,还是故意的,还是仅仅是一种未定义的行为)
尽管如此,您需要的是一种方法来设置在您的插槽变量中可用的值,一旦对象被实例化。
这样做的一个明显方法是将Meta.__init__中的逻辑传递给baseclass __init__方法,并在那里设置值(将defaults dict附加到类本身)。然后,任何调用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另一种方法是创建知道默认值的特殊描述符。更改添加前缀(例如"_")的时隙变量名,并使用这些描述符访问它们。这有点简单,虽然它比写元类__call__更复杂,但ou的优点是能够在描述符本身上添加额外的保护代码(例如:拒绝分配不同类型的默认值)。
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]: 1000https://stackoverflow.com/questions/66026576
复制相似问题