新版本:
我对之前的回答有些失望,所以我决定稍微重新写一下:
首先,看一下 DynamicClassAttribute
的源代码,你可能会注意到它看起来非常像普通的 property
。除了 __get__
方法外:
def __get__(self, instance, ownerclass=None):
if instance is None:
if self.__isabstractmethod__:
return self
raise AttributeError()
elif self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(instance)
这意味着如果要访问一个非抽象的DynamicClassAttribute
类属性时,它会引发AttributeError
而不是返回self
。对于实例,if instance:
的结果为True
,且__get__
与property.__get__
相同。
对于普通类而言,调用该属性时只会产生一个可见的AttributeError
:
from types import DynamicClassAttribute
class Fun():
@DynamicClassAttribute
def has_fun(self):
return False
Fun.has_fun
AttributeError - Traceback (most recent call last)
这本身并不是很有帮助,直到你查看使用metaclass
时的"类属性查找"过程(我在这篇博客中找到了一张好看的图片)。因为如果一个属性引发了AttributeError
并且该类具有元类, Python 就会查看metaclass.__getattr__
方法,并查看是否可以解决该属性。通过以下最小示例来说明:
from types import DynamicClassAttribute
class Funny(type):
def __getattr__(self, value):
print('search in meta')
return Funny.dynprop
dynprop = 'Meta'
class Fun(metaclass=Funny):
def __init__(self, value):
self._dynprop = value
@DynamicClassAttribute
def dynprop(self):
return self._dynprop
然后就到了“动态”的部分。如果您在类上调用 dynprop
,它会搜索元数据并返回元数据的 dynprop
:
Fun.dynprop
打印出:
search in meta
'Meta'
因此,我们调用了 metaclass.__getattr__
并返回原始属性(该属性与新属性具有相同的名称)。
对于实例,将返回 Fun
实例的 dynprop
:
Fun('Not-Meta').dynprop
我们获取了被覆盖的属性:
'Not-Meta'
我的结论是,如果您希望子类拥有与元类中使用的名称相同的属性,则DynamicClassAttribute
非常重要。在实例上,它被屏蔽了,但如果在类上调用它,它仍然可以访问。
我也讨论了旧版本中Enum
的行为,因此我将其保留在这里:
旧版本
如果您怀疑子类中设置的属性和基类上的属性可能存在命名冲突,则DynamicClassAttribute
非常有用(对于这一点我不太确定)。
您需要至少了解一些关于元类的基础知识,因为如果不使用元类,这将无法工作(关于如何调用类属性的良好解释可以在此博客文章中找到),因为使用元类时属性查找略有不同。
假设您有:
class Funny(type):
dynprop = 'Very important meta attribute, do not override'
class Fun(metaclass=Funny):
def __init__(self, value):
self._stub = value
@property
def dynprop(self):
return 'Haha, overridden it with {}'.format(self._stub)
然后调用:
Fun.dynprop
0x1b3d9fd19a8所对应的属性
在实例上我们得到:
Fun(2).dynprop
'哈哈,用2覆盖了它'
糟糕...它已经丢失了。但是我们可以使用元类(metaclass)
的特殊查找:让我们实现一个__getattr__
(回退),并将dynprop
实现为DynamicClassAttribute
。因为根据文档来看,这就是它的目的——如果在类上调用,则会回退到__getattr__
:
from types import DynamicClassAttribute
class Funny(type):
def __getattr__(self, value):
print('search in meta')
return Funny.dynprop
dynprop = 'Meta'
class Fun(metaclass=Funny):
def __init__(self, value):
self._dynprop = value
@DynamicClassAttribute
def dynprop(self):
return self._dynprop
现在我们访问类属性:
Fun.dynprop
输出:
search in meta
'Meta'
所以我们调用了
metaclass.__getattr__
并返回原始属性(该属性与新属性使用相同的名称定义)。
对于实例:
Fun('Not-Meta').dynprop
我们获取被覆盖的属性:
'Not-Meta'
考虑到我们可以使用元类重新定义但被重写的属性来进行重定向而无需创建实例,因此情况并不太糟。这个示例是与Enum
相反的操作,其中您在子类上定义属性:
from enum import Enum
class Fun(Enum):
name = 'me'
age = 28
hair = 'brown'
并且希望默认情况下访问这些事后定义的属性。
Fun.name
# <Fun.name: 'me'>
但是你也想允许访问作为 DynamicClassAttribute
定义的 name
属性(返回变量实际具有的名称):
Fun('me').name
否则,你怎么能访问
28
的名称呢?
Fun.hair.age
Fun.hair.name
看到区别了吗?为什么第二个不返回<Fun.name:'me'>
呢?这是因为使用了DynamicClassAttribute
。所以你可以遮盖原始属性,但稍后可以“释放”它。这种行为与我示例中显示的相反,并且至少需要使用__new__
和__prepare__
。但是,为此,您需要知道它的确切工作方式,并在许多博客和stackoverflow答案中得到解释,这些答案可以比我更好地解释,因此我将跳过深入探讨(而且我不确定我能否快速解决它)。
实际用例可能很少,但给定时间,人们可能会考虑一些……
DynamicClassAttribute
文档上非常好的讨论:"我们添加它是因为我们需要它"