什么是DynamicClassAttribute以及如何使用它?

时间:2016-03-16 16:05:54

标签: python class python-3.4

从Python 3.4开始,有一个名为DynamicClassAttribute的描述符。文档说明:

  

types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)

     

将类的属性访问权限路由到__getattr__

     

这是一个描述符,用于定义在通过实例和类访问时行为不同的属性。实例访问仍​​然正常,但通过类访问属性将被路由到类的__getattr__方法;这是通过提出AttributeError来完成的。

     

这允许一个实例上的属性处于活动状态,并且在类上具有相同名称的虚拟属性(例如,参见Enum)。

     

版本3.4中的新功能。

它显然用于enum module

# DynamicClassAttribute is used to provide access to the `name` and
# `value` properties of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# to have members named `name` and `value`.  This works because enumeration
# members are not set directly on the enum class -- __getattr__ is
# used to look them up.

@DynamicClassAttribute
def name(self):
    """The name of the Enum member."""
    return self._name_

@DynamicClassAttribute
def value(self):
    """The value of the Enum member."""
    return self._value_

我意识到enums are a little special,但我不明白这与DynamicClassAttribute的关系。这些属性是动态是什么意思,这与普通属性有何不同,我如何使用DynamicClassAttribute来获得优势呢?

2 个答案:

答案 0 :(得分:14)

新版本:

我对之前的回答感到有些失望,所以我决定重写一下:

首先看看the source code of DynamicClassAttribute,您可能会注意到它看起来非常像普通的property。除了__get__ - 方法:

def __get__(self, instance, ownerclass=None):
    if instance is None:
        # Here is the difference, the normal property just does: return self
        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(最近一次调用最后一次)

在您查看" Class属性查找"之前,它本身并没有多大帮助。程序何时使用metaclass es(我在this blog中找到了一个很好的图像)。 因为如果属性引发AttributeError并且该类具有元类python,则查看metaclass.__getattr__方法并查看是否可以解析该属性。用一个最小的例子说明这一点:

from types import DynamicClassAttribute

# Metaclass
class Funny(type):

    def __getattr__(self, value):
        print('search in meta')
        # Normally you would implement here some ifs/elifs or a lookup in a dictionary
        # but I'll just return the attribute
        return Funny.dynprop

    # Metaclasses 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__并返回原始属性(使用与新属性相同的名称定义)。

对于实例,dynprop - 实例的Fun被返回:

Fun('Not-Meta').dynprop

我们获得了overriden属性:

'Not-Meta'

我的结论是,如果您希望允许子类具有与元类中使用的名称相同的属性,则DynamicClassAttribute很重要。你会在实例上隐藏它,但如果你在课堂上调用它,它仍然可以访问。

我确实在旧版本中进入Enum的行为,所以我把它留在了这里:

旧版本

如果您怀疑在子类上设置的属性与基类上的属性之间可能存在命名冲突,那么DynamicClassAttribute只是有用(我在这一点上并不确定)

您至少需要了解一些有关元类的基础知识,因为如果不使用元类,这将无法工作(可以在this blog post中找到关于如何调用类属性的一个很好的解释),因为属性查找与元类略有不同

假设你有:

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。因为根据它documentation的目的 - 如果在课堂上调用__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

现在我们访问class-attribute:

search in meta
'Meta'

打印:

metaclass.__getattr__

因此我们调用Fun('Not-Meta').dynprop 并返回原始属性(使用与新属性相同的名称定义)。

例如:

'Not-Meta'

我们获得了overriden属性:

Enum

考虑到我们可以在不创建实例的情况下使用元类重新路由到先前定义但重写的属性,这一点也不错。此示例与from enum import Enum class Fun(Enum): name = 'me' age = 28 hair = 'brown' 相反,您可以在其中定义子类的属性:

Fun.name
# <Fun.name: 'me'>

并希望默认情况下访问这些后续定义的属性。

name

但您还希望允许访问定义为DynamicClassAttribute的{​​{1}}属性(返回变量实际具有的名称):

Fun('me').name
# 'name'

因为否则您如何访问28的名称?

Fun.hair.age
# <Fun.age: 28>
# BUT:
Fun.hair.name
# returns 'hair'

看到区别?为什么第二个不会返回<Fun.name: 'me'>?这是因为DynamicClassAttribute的使用。因此,您可以隐藏原始属性,但是&#34;发布&#34;再来一次。此行为与我的示例中显示的相反,至少需要使用__new____prepare__。但是为此你需要知道它是如何工作的,并且在许多博客和stackoverflow-answers中解释,这可以解释它比我更好,所以我将跳过那么多深度(并且我&#39;我不确定我能否在短时间内解决它。)

实际用例可能很少但是考虑到时间可以考虑一些......

关于DynamicClassAttribute的文档的非常好的讨论:"we added it because we needed it"

答案 1 :(得分:1)

DynamicClassAttribute背后的目的与元类/类名称冲突无关,而与类/实例名称冲突无关。

最初设计Enum时,成员不是标准属性-如果您查看类__dict__,则在那里看不到它们。当您尝试访问它们时,例如Color.RED,将触发元类的__getattr__,它将在另一个位置查找RED成员并返回它。

之所以这样做,是因为它允许Enum拥有与两个标准属性(namevalue)相同的成员名称。

如果您需要一个成员和一个具有相同名称的属性,可以在自己的Enum中使用它:

class MyBaseEnum(Enum):
    @DynamicClassAttribute
    def description(self):
        return '%s example' % self.name

class Example(MyBaseEnum):
    toy = 1
    shallow = 2
    in_depth = 3
    record = 4
    silly = 5
    description = 6