【问题标题】:Why Python Descriptor Work for Class Level Attribute and not for an Instance Level Attrubute为什么 Python 描述符适用于类级别属性而不适用于实例级别属性
【发布时间】:2018-05-27 21:04:15
【问题描述】:

为什么 Python 描述符适用于类级别属性而不适用于实例级别属性。

class PropDescriptor:

    def __init__(self,*args):
        print("Init {} {}".format(type(self),args))
        self.value = 0

    def __get__ (self,instance,owner):
        print("get using descriptor")
        return instance.instance_att

    def __set__(self, instance, value):
        print("set using descriptor")
        instance.instance_att = value


class TestClass:
    class_att = PropDescriptor()

    def __init__(self):
        self.instance_att = PropDescriptor()



t = TestClass()
print("set instance level...")
t.instance_att = 3

print("\nget instance level...")
print(t.instance_att)

print("\nset class level...")
t.class_att = 4

print("\nget class level...")
print(t.class_att)

输出:

Init <class '__main__.PropDescriptor'> ()
Init <class '__main__.PropDescriptor'> ()
set instance level...

get instance level...
3

set class level...
set using descriptor

get class level...
get using descriptor
4

instance_att 似乎没有使用描述符。

我找到了这个identical 问题,但它并没有真正回答这个问题所有答案都参考了帖子中的第二个问题。

The Python documentation 指定:

实例绑定

如果绑定到对象实例,则 a.x 被转换

进入通话:type(a).__dict__['x'].__get__(a, type(a))

但是: (我的代码等效)语句 type(t).__dict__["instance_att"] 的第一部分引发了 KeyError,因为 type(t).__dict__ 没有这样的属性。这是一个实例级别的att。不是吗?

我错过了什么?

【问题讨论】:

    标签: python descriptor


    【解决方案1】:

    我现在回复这个有点晚了,但我会尽力解释它。您的问题的两个部分的答案在于属性查找和设置在 python 内部实现的方式。

    1.为什么没有为实例级属性调用描述符?

    一旦您了解了a.x = somevalue 的内部翻译方式,这个问题的答案就很简单了。当您使用dot 运算符(如a.x="somevalue")设置属性时,它会转换为a.__setattribute__(x, somevalue),进而转换为type(a).__dict__['x'].__set__(x, somevalue)

    现在考虑您的示例self.instance_att__init__ 中定义,因此它不存在于type(a).__dict__ 中,这是在类级别定义的属性字典,因此描述符的__set__ 永远不会被调用。

    2。为什么描述符只在类级别定义?

    我不是百分百确定,但这是我的理解,因为 __getattribute____setattribute__ 是如何实现的,如果在实例级别定义(如上所述)描述符将根本不起作用,因此它们被定义为仅限类属性。

    此外,我认为要使描述符适用于实例属性,您必须为类覆盖 __getattribute____setattribute__。这就是被覆盖的最简单版本__getattribute__ 的外观。

    def __getattribute__(self, attr):
        attr_val = super().__getattribute__(attr)
        attr_type = type(attr_val)
        if hasattr(attr_type, '__get__'):
            return attr_type.__get__(attr_val, self, type(self))
        return attr_val
    

    这是相同的演示

    $ python3 -i instance_descriptors.py
    >>> t = TestClass()
    >>> t.instance_att
    get using descriptor
    200
    >>> 
    

    以类似的方式,您还需要覆盖__setattribute__ 以使属性设置生效。

    希望这会有所帮助!

    我强烈建议您阅读以下官方 python 文档以获取描述符,以完全理解该概念。

    Descriptors howto

    【讨论】:

    • 我想描述符不能作为实例属性工作的部分原因是由于描述符优先链:数据描述符>实例属性>非数据描述符>__getattr__。如果一个实例属性也可以是一个描述符,它会破坏这个链,这可能可能在几种情况下导致意外行为。其中之一就是这个问题本身,其中访问t.instance_att 会导致RecursionError
    【解决方案2】:

    快速回答:t.instance_attobject.__getattribute__ 处理,当在t 的实例字典中找到该值时,其定义简单地忽略了该值的__get____set__ 方法。

    较长的答案从解释如何处理属性查找开始。它们不是内置在语言中的,而是由某个类的__getattribute__ 方法处理的。很少被覆盖,这几乎总是意味着object.__getattribute__,它是由_PyObject_GenericGetAttrWithDict 在C 中实现的。

    我不在这里详细解释该算法,而是将您推荐给https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/,它有很棒的图表来解释对象属性查找和类属性查找。


    在 Stack Overflow 上至少有一个答案,其中包含图表,但我目前无法找到它,因此对于场外参考表示歉意。如果我能找到它,我会附上一个链接(或者可能将其作为副本关闭)。

    【讨论】:

    • 描述符仅支持类级别属性而不支持实例级别这一事实背后的想法是什么?有吗?
    • 我现在要删除我的答案,因为我找不到具体的解释来解释我现在对你为什么会得到观察到的行为的看法。
    • 我已经完全重写了答案,以强调观察到的行为只是object.__getattribute__ 实现方式的结果,这反映了描述符的预期用途而不是语言级别的决定。你可以覆盖__getattribute__ 为你的班级做任何你想做的事情。
    • 这是关于如何进行属性查找的技术解释。但它没有解释背后的逻辑。为什么类级别描述符有效,而实例级别的处理方式不同。
    猜你喜欢
    • 2010-09-30
    • 2016-02-18
    • 2020-03-09
    • 2011-10-24
    • 1970-01-01
    • 2018-09-06
    • 2021-12-24
    • 2019-05-15
    • 1970-01-01
    相关资源
    最近更新 更多