Python属性和描述符

5

我一直在阅读描述符指南,对这句话感到困惑:

如果一个实例的字典中有与数据描述符同名的条目,则数据描述符优先。

字典如何包含两个具有相同名称的项(普通条目和数据描述符)?或者说,作为描述符的属性是否没有存储在 __dict__ 中?

2个回答

6
数据描述符存在于类命名空间中,而实例属性存在于实例命名空间中(因此是instance.__dict__)。这些是两个单独的字典,因此这里没有冲突。
因此,对于任何给定的属性查找名称为foo的实例bar,Python还会按以下顺序查看它的类(type(bar),下面命名为C):
1. 查找C.foo。如果它是一个数据描述符,则查找将在此结束。返回C.foo.__get__(bar, C)。否则,Python将保存此结果以进行步骤3的查找(没有再次查找的意义)。 2. 如果C.foo不存在或是一个常规属性,则Python会查找bar.__dict__['foo']。如果存在,则返回它。请注意,如果C.foo是数据描述符,则永远不会到达此部分! 3. 如果bar.__dict__['foo']不存在,但C.foo存在,则使用C.foo。如果C.foo是一个(非数据)描述符,则返回C.foo.__get__(bar, C)
(请注意,在上述代码中,C.foo实际上是C.__dict__['foo'],但出于简单起见,我忽略了在类上的描述符访问。)
也许一个具体的例子会有所帮助;下面是两个描述符:其中一个是数据描述符(有一个__set__方法),另一个则不是。
>>> class DataDesc(object):
...     def __get__(self, inst, type_):
...         print('Accessed the data descriptor')
...         return 'datadesc value'
...     def __set__(self, inst, value):
...         pass   # just here to make this a data descriptor
...
>>> class OtherDesc(object):
...     def __get__(self, inst, type_):
...         print('Accessed the other, non-data descriptor')
...         return 'otherdesc value'
...
>>> class C(object):
...     def __init__(self):
...         # set two instance attributes, direct access to not
...         # trigger descriptors
...         self.__dict__.update({
...             'datadesc': 'instance value for datadesc',
...             'otherdesc': 'instance value for otherdesc',
...         })
...     datadesc = DataDesc()
...     otherdesc = OtherDesc()
...
>>> bar = C()
>>> bar.otherdesc  # non-data descriptor, the instance wins
'instance value for otherdesc'
>>> bar.datadesc  # data descriptor, the descriptor wins
Accessed the data descriptor
'datadesc value'

好的,数据描述符在类字典中。《描述符指南》似乎表明,在搜索类字典之前会先搜索实例字典:例如,a.x具有查找链,从a.dict ['x']开始,然后是type(a).__ dict__ ['x'],并继续穿过type(a)的基类,但不包括元类。 - Sir Visto
@SirVisto,这个教程正在简化中。如果您将数据描述符排除在外,那么顺序是完全正确的。在那一点引入数据描述符只会不必要地使叙述变得复杂。 - Martijn Pieters
实际上,文章后面有澄清: "该实现通过优先级链工作,使数据描述符优先于实例变量,实例变量优先于非数据描述符,并为__getattr__()分配最低优先级(如果提供)"。 - Sir Visto

1
考虑以下代码片段:
class X:
    @property
    def x(self):
        return 2

x1 = X()
x1.__dict__['x'] = 1
print(x1.x)

这段代码输出2,因为类定义的数据描述符优先于实例字典。

这背后的逻辑是什么?我直觉上会期望一个实例属性可以遮盖掉一个同名的类属性... - Sir Visto
@SirVisto:这使数据描述符能够完全控制访问(包括设置和删除)。如果实例字典具有优先权,那么del inst.foo有时会到达实例,有时会到达数据描述符。对于设置,当您执行inst.foo ='value'时,如果已经存在实例属性,是否应该有所影响?谁负责处理设置?通过始终使数据描述符负责,您可以清晰、一致地解决所有这些问题。 - Martijn Pieters
@SirVisto:另一个动机是确保某些类属性不会在实例中被意外覆盖。例如,__class__属性永远不应该在实例上设置,因为这样你就无法找到相应的类了,所以在类上,__class__是一个数据描述符。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接