为什么 __get__ 有所有者而 __set__ 和 __delete__ 没有?
Why does __get__ take an owner while __set__ and __delete__ do not?
来自Python data model documentation:
object.__get__(self, instance, owner=None)
Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). The optional
owner
argument is the owner class, whileinstance
is the instance that the attribute was accessed through, orNone
when the attribute is accessed through theowner
.This method should return the computed attribute value or raise an
AttributeError
exception.PEP 252 specifies that
__get__()
is callable with one or two arguments. Python’s own built-in descriptors support this specification; however, it is likely that some third-party tools have descriptors that require both arguments. Python’s own__getattribute__()
implementation always passes in both arguments whether they are required or not.
object.__set__(self, instance, value)
Called to set the attribute on an instance
instance
of the owner class to a new value, value.Note, adding
__set__()
or__delete__()
changes the kind of descriptor to a “data descriptor”. See Invoking Descriptors for more details.
object.__delete__(self, instance)
Called to delete the attribute on an instance
instance
of the owner class.
为什么 __get__
接受 owner
而 __set__
而 __delete__
不接受?
这是否意味着当描述符同时提供 __get__
和 __set__
时,
- 我们可以获得一个属性,无论它是属于所有者class的实例还是属于所有者class,
- 我们可以设置和删除属于所有者 class 实例的属性,但不能设置和删除属于所有者 class? 的属性
我的问题其实是
owner
主要用于获取 class 本身的属性,而不是实例。当您检索实例的属性时,owner
参数是多余的,因为它只是 type(instance)
.
__set__
不适用于 class 本身的属性设置,因此它对 owner
.
让我们考虑对 原始对象 的属性访问,它最终在 拥有的 class 上找到描述符,并且调用其 __get__
、__set__
或 __delete__
方法。
- 对于
__get__
,关于原始对象是拥有者class的实例还是子class的信息是必要的,因为classmethod
add a subclass of the owning class as first argument before forwarding a call, and because Python 2 unbound methods 在转发呼叫之前检查第一个参数是否是拥有 class 的实例(第二个原因现在只是历史原因,因为 Python 3 用普通函数替换了未绑定的方法)。所以__get__
需要两条信息:要从中检索属性值的原始对象,以及原始对象是拥有 class.[=99= 的实例还是子class ] - 对于
__set__
和__delete__
,关于原始对象是否是拥有者class的实例或子class的信息是不是必要,因为仅当原始对象是所属对象class 的实例时才调用这些方法,因为如果当原始对象是所属对象的子class 时也调用它们class 不可能更改错误的描述符,因为 class 字典是只读的types.MappingProxyType
。所以__set__
需要两条信息:设置属性值的原始对象和属性值。__delete__
需要一条信息:要从中删除属性值的原始对象。
提供此信息的一种直接方法是使用以下参数设计描述符 API:
- 用于提供原始对象的
origin
对象参数(因此对于__get__
、__set__
和__delete__
); - 一个
isinstance
布尔参数,用于提供原始对象是实例还是拥有 class 的子class(因此仅适用于__get__
); - 一个
value
对象参数,用于提供属性值(因此仅适用于__set__
)。
但是 Guido van Rossum 对描述符采用了不同但等效的设计 API:
- 一个
instance
对象参数,用于在它是拥有 class 的实例时提供原始对象(因此对于__get__
、__set__
和__delete__
), 以及原始对象是拥有 class 的一个实例还是子 class 对后者使用None
(所以只对__get__
); - 一个
owner
类型参数,用于在它是拥有 class 的子 class 时提供原始对象(因此仅适用于__get__
); - 一个
value
对象参数,用于提供属性值(因此仅适用于__set__
)。
他在 PEP 252: Making Types Look More Like Classes 中指定了描述符 API 的设计,发表于 2001 年 4 月 19 日:
Specification of the attribute descriptor API Attribute descriptors
Attribute descriptors may have the following attributes. In the examples,
x
is an object,C
isx.__class__
,x.meth()
is a method, andx.ivar
is a data attribute or instance variable. All attributes are optional -- a specific attribute may or may not be present on a given descriptor. An absent attribute means that the corresponding information is not available or the corresponding functionality is not implemented.
__name__
: the attribute name. Because of aliasing and renaming, the attribute may (additionally or exclusively) be known under a different name, but this is the name under which it was born. Example:C.meth.__name__ == 'meth'
.__doc__
: the attribute's documentation string. This may beNone
.__objclass__
: the class that declared this attribute. The descriptor only applies to objects that are instances of this class (this includes instances of its subclasses). Example:C.meth.__objclass__ is C
.__get__()
: a function callable with one or two arguments that retrieves the attribute value from an object. This is also referred to as a "binding" operation, because it may return a "bound method" object in the case of method descriptors. The first argument,X
, is the object from which the attribute must be retrieved or to which it must be bound. WhenX
isNone
, the optional second argument,T
, should be meta-object and the binding operation may return an unbound method restricted to instances ofT
. When bothX
andT
are specified,X
should be an instance ofT
. Exactly what is returned by the binding operation depends on the semantics of the descriptor; for example, static methods and class methods (see below) ignore the instance and bind to the type instead.__set__()
: a function of two arguments that sets the attribute value on the object. If the attribute is read-only, this method may raise aTypeError
orAttributeError
exception (both are allowed, because both are historically found for undefined or unsettable attributes). Example:C.ivar.set(x, y)
~~x.ivar = y
.
感谢Martijn Pieters for the arguments used in this answer (cf. our discussion in comments of