很多时候我们可能需要对某个实例的属性加上除了修改、访问之外的其他处理逻辑,例如 类型检查、数值校验等,就需要用到描述器 《Python Cookbook》
我们可以使用 Python 自带的 property
装饰器 来控制属性的访问,下面这个例子通过 property
控制了 Person 的 age 属性的访问和修改
class Person: |
试一试,的确如代码写的一样,对属性的类型进行了检查,而且使用了 property
装饰器之后,对 age 方法的访问和对属性的访问一样,不需要加 ()
a = Person() |
那么 property
是怎么实现的呢,这就要说到本文的主题 描述器了
描述器
Python 有三个特殊方法,__get__
、__set__
、__delete__
,用于覆盖属性的一些默认行为,如果一个类定义了其中一个方法,那么它的实例就是描述器
下面是一个简单的描述器的示例,Descriptor 是一个实现了 __get__
、__set__
的类,可以为其实例访问和修改时打印信息
class Descriptor: |
描述器是一种代理机制,对属性的操作由这个描述器来代理
访问: __get__(self, instance, cls) # instance 代表实例本身,cls 表示类本身,使用类直接访问时,instance 为 None |
下面这个例子列出了不同情况下 instance
和 cls
的值
class TestDescriptor: |
f = F() |
getattribute
描述器的 __get__
方法 是通过 __getattribute__
调用的,实际上,Python 中访问实例属性时,__getattribute__
就会被调用,__getattribute__
会查找整个继承链,直到找到属性,如果没有找到属性,但是定义了 __getattr__
,那么就会调用 __getattr__
去查找属性,否则抛出 AttributeError
__getattribute__
的代码用 Python 实现如下
def __getattribute__(self, key): |
可以做个测试,重写 __getattribute__
class Descriptor: |
访问描述器被 __getattribute__
拦截了
c.d |
data-descriptor and no-data descriptor
如果一个实例只定义了 __get__
那么,它就是一个非资料描述器 no-data descriptor
,如果同时定义了 __get__
和 __set__
那么就是资料描述器 data descriptor
它们的区别在于,如果实例字典中有与描述器同名的属性,如果是资料描述器,则优先使用资料描述器,否则使用实例字典中的属性
class AbsPriorityDescriptor: |
测试,可以看出来,资料描述器 a
忽略了实例字典的值,而非资料描述器则被覆盖
c = C() |
一些例子
实现类型检查
class Descriptor: |
class Person: |
测试
c = Person() |
property 的实现
虽然 property
是 C 代码实现的,但是我们可以模拟出 Python 的 Property
class Property: |
使用自定义的 Property
class A: |
staticmethod 实现
class StaticMethod: |
classmethod 实现
class ClassMethod: |
参考资料