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
个对象。
我担心整个答案可能等同于“这就是它的工作原理”,但我希望它至少比这更能说明问题。
我有这个描述符,它是“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
个对象。
我担心整个答案可能等同于“这就是它的工作原理”,但我希望它至少比这更能说明问题。