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

ger*_*rit 23 python class python-3.4

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

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

将类的属性访问路由到__getattr__.

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

这允许在实例上具有活动属性,并且在类上具有相同名称的虚拟属性(有关示例,请参阅枚举).

版本3.4中的新功能.

它显然用在枚举模块中:

# 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_
Run Code Online (Sandbox Code Playgroud)

我意识到枚举有点特别,但我不明白这与它有什么关系DynamicClassAttribute.这些属性是动态的,这与普通属性有什么不同,我如何利用DynamicClassAttribute我的优势?

MSe*_*ert 17

新版本:

我对之前的回答有点失望所以我决定重写一下:

首先来看看源代码,DynamicClassAttribute您可能会注意到,它看起来非常像正常property.除了__get__-method:

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)
Run Code Online (Sandbox Code Playgroud)

所以这意味着如果你想在类上访问一个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
Run Code Online (Sandbox Code Playgroud)

AttributeError - Traceback(最近一次调用最后一次)

使用metaclasses(我在本博客中找到了一个很好的图像)之前,看看"类属性查找"过程,这本身并不是很有帮助.因为如果一个属性引发一个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
Run Code Online (Sandbox Code Playgroud)

这里出现了"动态"部分.如果你打电话给dynprop类,它将在元搜索并返回元dynprop:

Fun.dynprop
Run Code Online (Sandbox Code Playgroud)

打印:

search in meta
'Meta'
Run Code Online (Sandbox Code Playgroud)

所以我们调用了metaclass.__getattr__并返回了原始属性(使用与新属性相同的名称定义).

虽然实例dynprop中的Fun-instance返回:

Fun('Not-Meta').dynprop
Run Code Online (Sandbox Code Playgroud)

我们得到overriden属性:

'Not-Meta'
Run Code Online (Sandbox Code Playgroud)

我的结论是,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)
Run Code Online (Sandbox Code Playgroud)

然后打电话:

Fun.dynprop
Run Code Online (Sandbox Code Playgroud)

属性为0x1b3d9fd19a8

在我们获得的实例上:

Fun(2).dynprop
Run Code Online (Sandbox Code Playgroud)

'哈哈,用2来覆盖它'

坏......它丢了.但是等等我们可以使用metaclass特殊查找:让我们实现一个__getattr__(回退)并实现dynpropas 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
Run Code Online (Sandbox Code Playgroud)

现在我们访问class-attribute:

Fun.dynprop
Run Code Online (Sandbox Code Playgroud)

打印:

search in meta
'Meta'
Run Code Online (Sandbox Code Playgroud)

所以我们调用了metaclass.__getattr__并返回了原始属性(使用与新属性相同的名称定义).

例如:

Fun('Not-Meta').dynprop
Run Code Online (Sandbox Code Playgroud)

我们得到overriden属性:

'Not-Meta'
Run Code Online (Sandbox Code Playgroud)

考虑到我们可以在不创建实例的情况下将元类重新路由到先前定义但重写的属性,这也不算太糟糕.此示例与完成相反,Enum您可以在子类上定义属性:

from enum import Enum

class Fun(Enum):
    name = 'me'
    age = 28
    hair = 'brown'
Run Code Online (Sandbox Code Playgroud)

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

Fun.name
# <Fun.name: 'me'>
Run Code Online (Sandbox Code Playgroud)

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

Fun('me').name
# 'name'
Run Code Online (Sandbox Code Playgroud)

因为否则你怎么能访问这个名字28

Fun.hair.age
# <Fun.age: 28>
# BUT:
Fun.hair.name
# returns 'hair'
Run Code Online (Sandbox Code Playgroud)

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

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

关于文档的非常好的讨论DynamicClassAttribute:"我们添加它因为我们需要它"


Eth*_*man 6

什么是动态类属性

ADynamicClassAttribute是类似于 的描述符propertyDynamic是名称的一部分,因为根据是通过类还是通过实例访问它,您会得到不同的结果:

  • property实例访问与修饰的任何方法相同,并且只是运行,返回其结果

  • 类访问会引发AttributeError; 当这种情况发生时,Python 会搜索每个父类(通过mro)来查找该属性——当它没有找到它时,它会调用该类的元类__getattr__来最后一次查找该属性。 __getattr__当然,可以做任何它想做的事情——在EnumMeta __getattr__类中_member_map_查找所请求的属性是否存在的情况下,如果存在则返回它。附带说明一下:所有搜索都会对性能产生严重影响,这就是为什么我们最终将所有与 s 没有名称冲突的成员放入DynamicClassAttributeEnum 类中。__dict__

我该如何使用它?

您可以像平常一样使用它property——唯一的区别是您在为其他枚举创建基类时使用它。例如,Enumfrom aenum1有三个保留名称:

  • name
  • value
  • values

values是否支持具有多个值的 Enum 成员。该类实际上是:

class Enum(metaclass=EnumMeta):

    @DynamicClassAttribute
    def name(self):
        return self._name_

    @DynamicClassAttribute
    def value(self):
        return self._value_

    @DynamicClassAttribute
    def values(self):
        return self._values_
Run Code Online (Sandbox Code Playgroud)

现在任何人都aenum.Enum可以拥有values会员而不会搞砸Enum.<member>.values


1声明:我是Python stdlibEnumenum34backportAdvanced Enumeration ( aenum)库的作者 。