先看一段代码,这段代码来自《流畅的python》

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Quantity:

    def __init__(self, storage_name):
        self.storage_name =storage_name

    def __set__(self, instance, value):
        if value > 0:
            # setattr(instance, self.storage_name, value)
            instance.__dict__[self.storage_name] = value
        else:
            raise ValueError('value must be > 0')

    def __get__(self, instance, owner):
        return getattr(instance, self.storage_name)


class LineItem:
    weight1 = Quantity('weight')
    price1 = Quantity('price')

    def __init__(self, description, weight, price):
        self.description = description
        self.weight1 = weight
        self.price1 = price

    def subtotal(self):
        return self.weight1 * self.price1

如果用setattr(instance, self.storage_name, value)替换 instance.__dict__[self.storage_name] = value会出现什么结果呢?答案是无限循环。那为什么呢?

根据描述器协议

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
descr.__get__(self, obj, type=None) -> value

descr.__set__(self, obj, value) -> None

descr.__delete__(self, obj) -> None

以上就是全部定义这些方法中的任何一个的对象被视为描述器并在被作为属性时覆盖其默认行为

如果一个对象定义了 __set__()  __delete__()则它会被视为数据描述器 仅定义了 __get__() 的描述器称为非数据描述器它们通常被用于方法但也可以有其他用途)。

数据和非数据描述器的不同之处在于如何计算实例字典中条目的替代值如果实例的字典具有与数据描述器同名的条目则数据描述器优先如果实例的字典具有与非数据描述器同名的条目则该字典条目优先

为了使数据描述器成为只读的应该同时定义 __get__()  __set__() 并在 __set__() 中引发 AttributeError 用引发异常的占位符定义 __set__() 方法使其成为数据描述器

可以知道Quantity是一个数据描述符,LineItem实例有和数据描述符同名的属性,因此按描述器协议会优先计算描述器字典中同名属性。所以setattr 所作的赋值操作其实是给数据描述符对应的属性赋值,又会调用__set__方法,所以才导致无限循环