3

From the 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.

Why does __get__ take an owner while __set__ and __delete__ do not?

Does it mean that when a descriptor supplies both __get__ and __set__,

  • we can get an attribute no matter whether it belongs to an instance of the owner class or to the owner class,
  • we can set and delete an attribute when it belongs to an instance of the owner class but not when it belongs to the owner class?

My question is actually part of this one.

4

2 回答 2

6

owner主要用于获取类本身的属性,而不是实例。当您检索实例的属性时,该owner参数是多余的,因为它只是type(instance).

__set__不适用于设置类本身的属性,所以对owner.

于 2017-07-06T00:09:32.133 回答
0

让我们考虑一个对原始对象的属性访问,它最终在拥有的类上找到一个描述符并调用它的__get__,__set____delete__方法。

  • 因为__get__,关于原始对象是所属类的实例还是子类的信息是必要的,因为classmethod在转发调用之前添加所属类的子类作为第一个参数,并且因为 Python 2未绑定方法检查第一个参数是在转发调用之前拥有类的实例(第二个原因现在只是历史性的,因为 Python 3 用普通函数替换了未绑定的方法)。因此__get__需要两条信息:要从中检索属性值的原始对象,以及原始对象是所属类的实例还是子类。
  • 对于__set____delete__,关于源对象是否是所属类的实例或子类的信息是不必要的,因为这些方法仅在源对象是所属类的实例时才被调用,因为如果它们在源对象时也被调用object 是所属类的子类,因为类字典是只读的,所以不可能更改错误的描述符types.MappingProxyType。所以__set__需要两条信息:设置属性值的原始对象和属性值。并且__delete__需要一条信息:要从中删除属性值的原始对象。

提供此信息的一种直接方法是使用以下参数设计描述符 API:

  • 用于提供原始对象的origin对象参数(对于__get____set____delete__);
  • 一个isinstance布尔参数,用于提供原始对象是拥有类的实例还是子类(因此仅适用于__get__);
  • 用于提供属性值的value对象参数(因此仅适用于__set__)。

然而 Guido van Rossum 对描述符 API 采用了不同但等效的设计:

  • 一个instance对象参数,用于在它是拥有类的实例时提供原始对象(对于__get____set____delete__),以及原始对象是拥有类的实例还是子类,通过使用None后者(仅对于__get__);
  • 一个owner类型参数,用于在它是所属类的子类时提供原始对象(因此仅适用于__get__);
  • 用于提供属性值的value对象参数(因此仅适用于__set__)。

他在2001 年 4 月 19 日发布的PEP 252: Making Types Look More Like Classes中详细说明了描述符 API 的设计:

属性描述符 API 属性描述符的规范

属性描述符可以具有以下属性。在示例中,x是对象,Cx.__class__x.meth()是方法,并且x.ivar是数据属性或实例变量。所有属性都是可选的——一个特定的属性可能出现在给定的描述符上,也可能不出现。缺少属性意味着相应的信息不可用或相应的功能未实现。

  • __name__: 属性名称。由于别名和重命名,该属性可能(另外或唯一)以不同的名称为人所知,但这是它诞生的名称。示例:C.meth.__name__ == 'meth'
  • __doc__: 属性的文档字符串。这可能是None
  • __objclass__: 声明此属性的类。描述符仅适用于作为此类实例的对象(这包括其子类的实例)。示例:C.meth.__objclass__ is C
  • __get__(): 一个可使用一个或两个参数调用的函数,用于从对象中检索属性值。这也称为“绑定”操作,因为在方法描述符的情况下它可能返回“绑定方法”对象。第一个参数X是必须从中检索或绑定属性的对象。当XNone时,可选的第二个参数 ,T应该是元对象,并且绑定操作可能会返回限制为 的实例的未绑定方法T。当同时指定XandT时,X应该是T. 绑定操作返回的确切内容取决于描述符的语义;例如,静态方法和类方法(见下文)忽略实例并改为绑定到类型。
  • __set__():设置对象属性值的两个参数的函数。如果属性是只读的,则此方法可能会引发异常(两者都是允许的,因为在历史上都可以找到未定义或不可设置的属性)TypeErrorAttributeError示例:C.ivar.set(x, y)~~ x.ivar = y

我感谢Martijn Pieters在这个答案中使用的论点(参见我们在这篇文章的评论中的讨论)。

于 2021-03-24T23:18:55.067 回答