为什么 __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, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner.

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__ 时,

我的问题其实是的一部分。

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 is x.__class__, x.meth() is a method, and x.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 be None.
  • __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. When X is None, the optional second argument, T, should be meta-object and the binding operation may return an unbound method restricted to instances of T. When both X and T are specified, X should be an instance of T. 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 a TypeError or AttributeError 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 ).