Python 描述符 class 属性如何分配给实例成员

Python descriptor how are class attributes getting assigned to instance member

我有这个描述符,它是“Fluent Python”一书中的一个例子。我无法理解 class 属性如何变为 LineItem class 的 __init__ 中的实例属性。请在该功能的评论中查看我的问题。

class Quantity:
    def __init__(self, managed_attribute_name):
        print(f'managing attribute {managed_attribute_name}')
        self._managed_attribute_name = managed_attribute_name

    def __get__(self, instance, owner):
        print(f'{self._managed_attribute_name} get')
        return instance.__dict__[self._managed_attribute_name]

    def __set__(self, instance, value):
        print(f'{self._managed_attribute_name} set {value}')
        if value > 0:
            instance.__dict__[self._managed_attribute_name] = value
            
class LineItem:
    # Managed Attributes
    weight = Quantity('weight')
    price = Quantity('price')

    def __init__(self, wt, pr):
        # What are these 2 lines doing? 
        # How do 'weight' and 'price' become instance members here 
        # and still maintaining their type Quantity
        self.weight = wt
        self.price = pr
        
li1 = LineItem(3, 4)
print(li1.weight)
li2 = LineItem(5, 6)
print(li2.price)

你是对的,这不是一般的函数或特别是构造函数的一般工作方式。通常,self.weight = wt 的效果是名称 self.weight 只是与对象 wt 相关联,而不管 wt 可能恰好是什么类型的对象。

这里的不同之处在于描述符的魔力。数量 class 是一个描述符,因为它定义了方法 __get____set__。当您在 class 级别定义一个 Quantity 对象并稍后引用具有相同名称的实例属性时,如您的示例所示,Python 将描述符的覆盖访问方法应用于实例属性。没有任何可见的机制可以实现这一点。 class 级别的描述符的定义实际上是一个声明,告诉 Python,“当我用这个名称定义一个实例属性时,在它上面使用这个描述符的访问方法。”

进一步不可见的描述符魔法为 Quantity 方法提供了 instance 参数。例如,如果您尝试打印出 LineItem.weight 或其类型,则尝试将失败,因为 instance 参数将(适当地)为 None.

引用 Descriptor HowTo Guide,“属性访问的默认行为是从对象的字典中获取、设置或删除属性。例如,a.x 有一个查找链开始使用 a.__dict__['x'],然后 type(a).__dict__['x'],并继续通过 type(a) 的基 classes 排除 metaclasses。如果查找的值是定义一个的对象描述符方法,然后 Python 可能会覆盖默认行为并改为调用描述符方法。” type(a).__dict__['x'] 中的查找是描述符行为如何从 class 传播到实例。

示例中实例的weight和price属性不是Quantity对象。它们是通过 Quantity 的方法访问的 int 个对象。

我担心整个答案可能等同于“这就是它的工作原理”,但我希望它至少比这更能说明问题。