在 Python 教程的前两章中,我们学习了如何使用Python 属性,甚至如何实现自定义属性类。在本章中,您将了解描述符的详细信息。
描述符是在 2.2 版中引入 Python 的。那个时候《Python2.2 的新特性》中提到:“新的类模型背后的一个大思想是,使用描述符来描述对象属性的 API 已经被形式化了。描述符指定了一个属性的值,说明是否它是一个方法或一个字段。使用描述符 API,静态方法和类方法以及更多奇特的构造成为可能。”
描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。这些方法是__get__()
,__set__()
和__delete__()
。
如果为对象定义了这些方法中的任何一个,则称其为描述符。
它们的目的在于为程序员提供向类添加托管属性的能力。描述符被引入以__dict__
通过上述方法从对象的字典中获取、设置或删除属性。访问类属性将启动查找链。
让我们仔细看看正在发生的事情。假设我们有一个对象obj
:如果我们尝试访问一个属性(或属性)会发生ap
什么?“访问”属性意味着“获取”值,因此该属性用于例如打印函数或表达式内部。无论是obj
和类属于type(obj)
包含字典属性__dict__
。这种情况如下图所示:
obj.ap
有一个以 开头的查找链obj.__dict__['ap']
,即检查是否obj.ap
是字典的键obj.__dict__['ap']
。
如果ap
不是 的键obj.__dict__
,它将尝试查找type(obj).__dict__['ap']
。
如果obj
也没有包含在这个字典中,它会继续检查type(ap)
排除元类的基类。
我们在一个例子中证明了这一点:
类 A :
ca_A = "A 的类属性"
def __init__ ( self ):
self 。ia_A = "A 实例的实例属性"
class B ( A ):
ca_B = "B 的类属性"
def __init__ ( self ):
super () 。__init__ ()
自我。ia_B = "A 实例的实例属性"
x = B ()
print ( x . ia_B)
打印( x . ca_B )
打印( x . ia_A )
打印( x . ca_A )
输出:
实例的实例属性
B的类属性
实例的实例属性
A的类属性
如果我们调用,print(x.non_existing)
我们会得到以下异常:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-119192d61d5e> in <module>
----> 1 print(x.non_existing)
AttributeError: 'B' object has no attribute 'non_existing'
如果查找的值是定义描述符方法之一的对象,则 Python 可能会覆盖默认行为并改为调用描述符方法。这在优先链中发生的位置取决于定义了哪些描述符方法。
描述符提供了一个强大的通用协议,它是属性、方法、静态方法、类方法和 super() 背后的底层机制。需要描述符来实现 2.2 版中引入的所谓新样式类。“新样式类”是当今的默认设置。
描述符协议
通用描述符协议由三种方法组成:
descr.__get__(self, obj, type=None) -> value
descr.__set__(self, obj, value) -> None
descr.__delete__(self, obj) -> None
如果您定义了这些方法中的一个或多个,您将创建一个描述符。我们区分数据描述符和非数据描述符:
非数据描述符
如果我们只定义 __get__() 方法,我们就创建了一个非数据描述符,主要用于方法。
数据描述符
如果一个对象定义了 __set__() 或 __delete__(),它被认为是一个数据描述符。要创建只读数据描述符,请使用 __set__() 定义 __get__() 和 __set__() 并在调用时引发 AttributeError 。使用异常引发占位符定义 __set__() 方法足以使其成为数据描述符。
现在我们终于来到了下面代码中的简单描述符示例:
class SimpleDescriptor ( object ):
"""
一个可以设置和返回值的简单数据描述符
"""
def __init__ ( self , initval = None ):
print ( "__init__ of SimpleDecorator call with initval:" , initval )
self . __set__ ( self , initval )
def __get__ ( self , instance , owner ):
print ( instance , owner)
print ( 'Getting (Retrieving) self.val:' , self . val )
return self . val
def __set__ ( self , instance , value ):
print ( 'Setting self.val to ' , value )
self 。val = value
class MyClass ( object ):
x = SimpleDescriptor ( "green" )
m = MyClass ()
打印(米。x )
米。x = "黄色"
打印( m . x )
输出:
用 initval 调用 SimpleDecorator 的 __init__:green
将 self.val 设置为绿色
<__main__.MyClass 对象在 0x7efe93ff4a20> <class '__main__.MyClass'>
获取(检索)self.val:绿色
绿色
将 self.val 设置为黄色
<__main__.MyClass 对象在 0x7efe93ff4a20> <class '__main__.MyClass'>
获取(检索)self.val:黄色
黄色的
第三个参数owner
的__get__
永远是所有者类,并为用户提供了一个选项,做一些与被用来调用描述符的类。通常,即如果描述符是通过对象调用的