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

时间:2018-05-27 21:04:16

标签: python descriptor

为什么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。不是吗?

我错过了什么?

2 个答案:

答案 0 :(得分: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__是在类级别定义的属性的词典,因此是描述符的{ {1}}永远不会被呼叫。

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

答案 1 :(得分:0)

快速回答:t.instance_attobject.__getattribute__处理,其定义只是忽略了__get____set__方法的价值。 t的实例字典。

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

我不是在这里详细解释这个算法,而是引用https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/,它有很棒的图解释对象属性查找和类属性查找。

Stack Overflow至少有一个答案,包括图表,但我现在无法找到它,所以我为场外参考道歉。如果我能找到它,我将包含一个链接(或者可能将其作为副本关闭)。