为什么在 class 上设置描述符会覆盖描述符?
Why does setting a descriptor on a class overwrite the descriptor?
简单复制:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
class B(object):
v = VocalDescriptor()
B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor
这个问题有一个effective duplicate, but the duplicate was not answered, and I dug a bit more into the CPython source as a learning exercise. Warning: i went into the weeds. I'm really hoping I can get help from a captain who knows those waters。为了我自己和未来读者的利益,我试图尽可能明确地追踪我正在查看的电话。
我看到很多关于 __getattribute__
应用于描述符的行为的问题,例如查找优先级。 "Invoking Descriptors" just below For classes, the machinery is in type.__getattribute__()...
roughly agrees in my mind with what I believe is the corresponding CPython source in type_getattro
, which I tracked down by looking at "tp_slots" then where tp_getattro is populated 中的 Python 片段。 B.v
最初打印 __get__, obj=None, objtype=<class '__main__.B'>
这一事实对我来说很有意义。
我不明白的是,为什么赋值B.v = 3
会盲目覆盖描述符,而不是触发v.__set__
?我试图追踪 CPython 调用,再次从 "tp_slots", then looking at where tp_setattro is populated, then looking at type_setattro. type_setattro
appears to be a thin wrapper around _PyObject_GenericSetAttrWithDict. And there's the crux of my confusion: _PyObject_GenericSetAttrWithDict
appears to have logic that gives precedence to a descriptor's __set__
method 开始!!考虑到这一点,我无法弄清楚为什么 B.v = 3
盲目覆盖 v
而不是触发 v.__set__
.
免责声明 1:我没有使用 printfs 从源代码重建 Python,所以我不是
完全确定 type_setattro
是在 B.v = 3
.
期间调用的内容
免责声明 2:VocalDescriptor
并非旨在举例说明 "typical" 或 "recommended" 描述符定义。告诉我何时调用方法是一个冗长的空操作。
你是对的,B.v = 3
只是用一个整数覆盖了描述符(这是应该的)。在描述符协议中,__get__
is designed to be called as instance attribute or class attribute,但__set__
被设计为仅作为实例属性被调用。
为了 B.v = 3
调用描述符,描述符应该在元class 上定义,即在 type(B)
.
上定义
>>> class BMeta(type):
... v = VocalDescriptor()
...
>>> class B(metaclass=BMeta):
... pass
...
>>> B.v = 3
__set__
要在 B
上调用描述符,您可以使用实例:B().v = 3
即可。
B.v
也调用 getter 的原因是允许用户自定义 B.v
的功能,独立于 B().v
的功能。一种常见的模式是允许直接访问描述符实例,方法是在使用 class 属性访问时 returning 描述符本身:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
if obj is None:
return self
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
现在 B.v
会 return 像 <mymodule.VocalDescriptor object at 0xdeadbeef>
这样的实例,您可以与之交互。它实际上是描述符对象,定义为 class 属性,其状态 B.v.__dict__
在 B
.
的所有实例之间共享
当然,用户的代码可以准确定义他们想要 B.v
做什么,returning self
只是常见的模式。用于 classmethod
.
的纯 python 实现的 classmethod
is an example of a descriptor which does something different here, see the Descriptor HowTo Guide
与可用于独立自定义 B().v
和 B.v
的 __get__
不同,除非对实例进行属性访问,否则不会调用 __set__
。我认为使用相同的描述符 v
自定义 B().v = other
和 B.v = other
的目标并不普遍或有用,不足以使描述符协议进一步复杂化,特别是因为后者仍然可以通过无论如何,metaclass 描述符,如上文 BMeta.v
所示。
除非有任何覆盖,B.v
等同于 type.__getattribute__(B, "v")
,而 b = B(); b.v
等同于 object.__getattribute__(b, "v")
。如果定义,这两个定义都会调用结果的 __get__
方法。
请注意,对 __get__
的调用在每种情况下都不同。 B.v
传递 None
作为第一个参数,而 B().v
传递实例本身。在这两种情况下,B
作为第二个参数传递。
另一方面,B.v = 3
等同于 type.__setattr__(B, "v", 3)
,它 不 调用 __set__
.
我认为当前答案中 none 确实回答了您的问题。
Why does setting a descriptor on a class overwrite the descriptor?
在拥有描述符(例如 cls.descr = 3
或 del cls.descr
) 覆盖该描述符,因为否则无法更改错误的描述符(例如 descr.__set__(None, cls, 3)
或 descr.__delete__(None, cls)
引发异常),因为 class 字典(例如 cls.__dict__
)是只读 types.MappingProxyType
。如果您想覆盖 class 上的设置或删除属性,您始终可以在元 class 上定义描述符,该 class 是该元 class 的一个实例。所以 __set__
和 __delete__
总是传递一个拥有描述符的 class 的实例,这就是为什么它们没有 owner
参数。
在拥有描述符(例如 cls.descr
)的 class(或 class 的子class)上获取属性不会覆盖该描述符,因为它不会阻止更改错误的描述符(例如 descr.__get__(None, cls)
引发异常)。因此 __get__
被传递给拥有描述符的 class 的实例,或者 class(或 class 的子class)本身,即为什么它有一个 owner
参数。
更多信息见。
简单复制:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
class B(object):
v = VocalDescriptor()
B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor
这个问题有一个effective duplicate, but the duplicate was not answered, and I dug a bit more into the CPython source as a learning exercise. Warning: i went into the weeds. I'm really hoping I can get help from a captain who knows those waters。为了我自己和未来读者的利益,我试图尽可能明确地追踪我正在查看的电话。
我看到很多关于 __getattribute__
应用于描述符的行为的问题,例如查找优先级。 "Invoking Descriptors" just below For classes, the machinery is in type.__getattribute__()...
roughly agrees in my mind with what I believe is the corresponding CPython source in type_getattro
, which I tracked down by looking at "tp_slots" then where tp_getattro is populated 中的 Python 片段。 B.v
最初打印 __get__, obj=None, objtype=<class '__main__.B'>
这一事实对我来说很有意义。
我不明白的是,为什么赋值B.v = 3
会盲目覆盖描述符,而不是触发v.__set__
?我试图追踪 CPython 调用,再次从 "tp_slots", then looking at where tp_setattro is populated, then looking at type_setattro. type_setattro
appears to be a thin wrapper around _PyObject_GenericSetAttrWithDict. And there's the crux of my confusion: _PyObject_GenericSetAttrWithDict
appears to have logic that gives precedence to a descriptor's __set__
method 开始!!考虑到这一点,我无法弄清楚为什么 B.v = 3
盲目覆盖 v
而不是触发 v.__set__
.
免责声明 1:我没有使用 printfs 从源代码重建 Python,所以我不是
完全确定 type_setattro
是在 B.v = 3
.
免责声明 2:VocalDescriptor
并非旨在举例说明 "typical" 或 "recommended" 描述符定义。告诉我何时调用方法是一个冗长的空操作。
你是对的,B.v = 3
只是用一个整数覆盖了描述符(这是应该的)。在描述符协议中,__get__
is designed to be called as instance attribute or class attribute,但__set__
被设计为仅作为实例属性被调用。
为了 B.v = 3
调用描述符,描述符应该在元class 上定义,即在 type(B)
.
>>> class BMeta(type):
... v = VocalDescriptor()
...
>>> class B(metaclass=BMeta):
... pass
...
>>> B.v = 3
__set__
要在 B
上调用描述符,您可以使用实例:B().v = 3
即可。
B.v
也调用 getter 的原因是允许用户自定义 B.v
的功能,独立于 B().v
的功能。一种常见的模式是允许直接访问描述符实例,方法是在使用 class 属性访问时 returning 描述符本身:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
if obj is None:
return self
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
现在 B.v
会 return 像 <mymodule.VocalDescriptor object at 0xdeadbeef>
这样的实例,您可以与之交互。它实际上是描述符对象,定义为 class 属性,其状态 B.v.__dict__
在 B
.
当然,用户的代码可以准确定义他们想要 B.v
做什么,returning self
只是常见的模式。用于 classmethod
.
classmethod
is an example of a descriptor which does something different here, see the Descriptor HowTo Guide
与可用于独立自定义 B().v
和 B.v
的 __get__
不同,除非对实例进行属性访问,否则不会调用 __set__
。我认为使用相同的描述符 v
自定义 B().v = other
和 B.v = other
的目标并不普遍或有用,不足以使描述符协议进一步复杂化,特别是因为后者仍然可以通过无论如何,metaclass 描述符,如上文 BMeta.v
所示。
除非有任何覆盖,B.v
等同于 type.__getattribute__(B, "v")
,而 b = B(); b.v
等同于 object.__getattribute__(b, "v")
。如果定义,这两个定义都会调用结果的 __get__
方法。
请注意,对 __get__
的调用在每种情况下都不同。 B.v
传递 None
作为第一个参数,而 B().v
传递实例本身。在这两种情况下,B
作为第二个参数传递。
B.v = 3
等同于 type.__setattr__(B, "v", 3)
,它 不 调用 __set__
.
我认为当前答案中 none 确实回答了您的问题。
Why does setting a descriptor on a class overwrite the descriptor?
在拥有描述符(例如 cls.descr = 3
或 del cls.descr
) 覆盖该描述符,因为否则无法更改错误的描述符(例如 descr.__set__(None, cls, 3)
或 descr.__delete__(None, cls)
引发异常),因为 class 字典(例如 cls.__dict__
)是只读 types.MappingProxyType
。如果您想覆盖 class 上的设置或删除属性,您始终可以在元 class 上定义描述符,该 class 是该元 class 的一个实例。所以 __set__
和 __delete__
总是传递一个拥有描述符的 class 的实例,这就是为什么它们没有 owner
参数。
在拥有描述符(例如 cls.descr
)的 class(或 class 的子class)上获取属性不会覆盖该描述符,因为它不会阻止更改错误的描述符(例如 descr.__get__(None, cls)
引发异常)。因此 __get__
被传递给拥有描述符的 class 的实例,或者 class(或 class 的子class)本身,即为什么它有一个 owner
参数。
更多信息见