1.1。摘要
定义描述符,汇总描述符协议,并显示如何调用描述符。显示自定义描述符和几个Python内置描述符,包括函数,属性,静态方法和类方法。通过提供纯Python实现和示例应用程序来显示每个描述符的工作方式。

学习描述符不仅可以让您访问更多工具,还可以让您更深入地了解Python,并让您实现Python设计的优雅。

1.2。定义和介绍
一般而言,描述符是具有“绑定行为”的对象属性,并且其访问控制由描述符协议方法重写。这些方法是__get __(),__ set __()和__delete __()。具有这些方法的对象称为描述符。

属性的默认访问控制是从对象的字典(__dict__)中获取,设置和删除它。例如,a.x的搜索顺序是:a .__ dict __ ['x'],然后键入(a).__ dict __ ['x'],然后找到类型(a)的父类(不包括元类))。如果找到的值是描述符,Python将调用描述符方法来覆盖默认控件行为。此查找中发生此重写的位置取决于定义的描述符方法。请注意,描述符仅适用于新类。 (新类是从类型或对象继承的类)

描述符功能强大且广泛使用。描述符是属性,实例方法,静态方法,类方法和超级背后的实现机制。描述符广泛用于Python本身,以实现Python 2.2中引入的新类。描述符简化了底层的C代码,并为Python的日常编程提供了一组灵活的新工具。

1.3。描述符协议

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None


这是所有描述符方法。具有任一方法的对象都将成为描述符,在将其视为对象属性时会覆盖默认查找行为。

如果对象定义__get __()和__set __(),则称为数据描述符。仅定义__get __()的描述符称为非数据描述符(通常在方法中使用,尽管其他用途也是可能的)

数据描述符和非数据描述符之间的区别是字典相对于实例的优先级。如果存在与实例字典中的描述符同名的属性,则如果描述符是数据描述符,则优选数据描述符;如果它是非数据描述符,则首选字典中的属性。 (译者注:这就是为什么当实例A的方法和属性具有相同名称时,例如foo Python,实例字典中的属性在访问a.foo时会被优先访问,因为实例函数的实现是非数据描述符)

要创建只读数据描述符,需要定义__set__和__get__,并在__set__中抛出AttributeError异常。定义抛出异常的__set__方法足以使描述符成为数据描述符。

1.4。征集者的召唤
描述符可以直接调用如下:d .__ get __(obj)

但是,在访问属性时自动调用描述符更为常见。例如,obj.d将在obj的字典中查找D.如果D定义__get__方法,则将根据以下优先级规则调用d .__ get __(obj)。

调用的细节取决于obj是类还是实例。此外,描述符仅适用于新对象和新类。从对象继承的类称为新类。

对于对象,方法对象.__ getattribute __()将b.x更改为类型(b)。 _ _ dict _ _ ['x']。 _ _ get _ _(b,type(b))。具体实现基于以下优先级顺序:数据描述符优先于实例变量,实例变量优先于非数据描述符,__ getattr __()方法(如果包含在对象中)具有最低优先级。可以在Objects / object.c PyObject_GenericGetAttr()中查看完整的c语言实现。

对于类,方法类型.__ getattribute __()将B.x更改为B .__ dict __ ['x'] .__ get __(None,B)。在Python中描述它是:

def __getattribute__(self, key):
    "Emulate type_getattro() in Objects/typeobject.c"
    v = object.__getattribute__(self, key)
    if hasattr(v, '__get__'):
       return v.__get__(None, self)
    return v

一些重要的观点是:

调用描述符是因为__getattribute __()
覆盖__getattribute __()方法可防止正常的描述符调用
__getattribute __()仅适用于新类的实例
Object .__ getattribute __()和type .__ getattribute __()以不同方式调用__get __()
数据描述符始终优先于实例字典。
非数据描述符

super() 返回的对象同样有一个定制的 __getattribute__() 方法用来调用描述器。调用 super(B, obj).m() 时会先在 obj.__class__.__mro__ 中查找与B紧邻的基类A,然后返回 A.__dict__['m'].__get__(obj, A) 。如果不是描述器,原样返回 m 。如果实例字典中找不到 m ,会回溯继续调用 object.__getattribute__() 查找。(译者注:即在 __mro__ 中的下一个基类中查找)

注意:在Python 2.2中,如果 m 是一个描述器, super(B, obj).m() 只会调用方法 __get__() 。在Python 2.3中,非资料描述器(除非是个旧式类)也会被调用。 super_getattro() 的实现细节在: Objects/typeobject.c ,[del] 一个等价的Python实现在 Guido’s Tutorial [/del] (译者注:原文此句已删除,保留供大家参考)。

以上展示了描述器的机理是在 object, type, 和 super 的 __getattribute__() 方法中实现的。由 object 派生出的类自动的继承这个机理,或者它们有个有类似机理的元类。同样,可以重写类的 __getattribute__() 方法来关闭这个类的描述器行为。