描述器
什么是描述器?
一个类中定义了如下一个或多个魔术方法,这个类的实例就是描述器:
__get__,__set__,__delete__
通常需要两个类来构建描述器:
如果类B的类属性x,指向另一个类A的实例。被指向的A的实例就是描述器对象。B.x是描述器,B也是描述器的属主(owner)。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class A: def __get__(self, instance, owner): pass
def __set__(self, instance, value): pass
def __delete__(self, instance): pass
class B: x = A() pass
|
类属性的值,通常是一些已有类型的对象,比如字符串、列表等。
当使用了描述器,类属性就指向一个描述器对象,描述器通过三个魔术方法,可以自定义属性的行为。
描述器的分类
非数据描述器:
只定义了__get__
,就是非数据描述器(non-data descriptor)。
数据描述器:
定义了__get__
,且定义了__set__
或__set__ 与 __delete__
,就是数据描述器(data descriptor)。
属性搜索顺序
当一个实例与它所属的类有相同的属性名时:
非数据描述器,实例的属性搜索顺序:
__getattribute__
⟶ 默认搜索顺序 [1] ⟶ __getattr__
。也就是说,此时__get__无效。
数据描述器,会拦截实例属性字典的访问:
不会访问实例属性字典__dict__
。属性访问或修改会被描述器的__get__
, __set__
, __delete__
方法处理。
注意:
如果有 __getattribute__ 方法,不管有没有描述器,实例属性搜索时,都优先调用此方法,可以拦截一切(包括 实例.__dict__ 的访问也拦截)。
__getattribute__ 和 __getattr__ 又是做什么的?下文讲。
[1] 默认搜索顺序:
默认搜索顺序就是没有描述器时的搜索顺序,遵循如下规则:
实例的属性字典(__dict__) ⟶ 类的属性字典 ⟶ 类的父类的属性字典 ⟶ … ⟶ 祖先类object的属性字典
属性搜索顺序与类的继承有关。如果是单继承,属性(或方法)搜索路径是确定的,一直向上找。如果是多继承,就涉及到MRO(方法解析顺序)。Python3的MRO采用C3算法,在类被创建出来的时候,就计算出一个MRO有序列表。关于C3算法,见官方文档。
属性读写操作示例
B.x = 400,类属性赋值(赋值即重新定义),如果x是描述器,将被覆盖。
b.x = 500,非数据描述器时,将修改实例自己的属性(__dict__)。
b.x = 600,数据描述器时,将调用描述器的__set__
方法。
B.x,若x是描述器,调用描述器的__get__
方法。
b.x,若x是描述器,调用描述器的__get__
方法。
直接操作实例的__dict__
字典,可以绕开描述器对__get__
,__set__
等的调用。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| class A: def __init__(self): print('A().init') self.x = 101
def __get__(self, instance, owner): print('~~~~ A.get ~~~~') print(self) print(instance) print(owner) print('~~~~ A.get ~~~~') return getattr(instance, 'z', 'no_z_found')
def __set__(self, instance, value): print('~~~~~ A.set ~~~~') print(self) print(instance) print(value) print('~~~~~ A.set ~~~~') instance.z = value
def __delete__(self, instance): print('~~~~~ in delete') del instance.z
class B: x = A()
def __getattribute__(self, item): print('___ in getattribute ___')
return object.__getattribute__(self, item)
def __init__(self): print('B().init') self.x = 1000
b = B()
print('\n' + '-' * 30) print(B.x) print() print(b.x)
print('\n' + '#' * 30)
b.x = 456 print(b.x)
print('\n' + '=' * 30) print(b.__dict__) del b.x print(b.x) print(b.__dict__)
|
反射
上文提到的__getattribute__跟__get__有什么关系呢?实际上前者是反射相关的魔术方法。那什么是反射呢?
当我们需要用到对象的某个属性(或方法),但是由于某种原因无法确定这个属性是否存在,这时我们需要用一种特殊的机制,去访问和操作这个未知的属性,这种机制就称为反射(reflection)。反射就相当于一种自我检查机制。
反射机制不仅包括,要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息,改变程序状态或结构。总之一句话,反射指的是运行时获取类型定义信息,并且还能修改这些信息。
与反射相关的四个函数:getattr、setattr、delattr、hasattr
。与这些函数相关的四个魔术方法:__getattr__, __setattr__, __delattr__, __getattribute__
。见下面表格:
魔术方法 |
含义 |
__getattr__ |
此方法只影响实例。实例属性默认搜索顺序:实例自己(的__dict__,后同)、实例的类、类的父类、父类的父类、object祖先类。若从这个顺序中没有找到属性,会抛出AttributeError 异常,但类中定义了__getattr__ ,实例将捕获异常,并调用此方法。此方法可用于实例没有找到属性时,拦截异常,做一些操作。 |
__setattr__ |
此方法只影响实例。self.x = x, setattr(self, ‘x’, x) 等涉及到修改实例属性的操作时,如果定义了__setattr__ ,就会调用此方法。此方法可以拦截实例属性修改操作的默认行为。比如将实例的属性存储在新的字典中,而不是存储在默认字典__dict__。 |
__delattr__ |
此方法只影响实例。del self.x, delattr(self, ‘x’) 等涉及删除实例属性的操作时,如果定义了__delattr__ ,将会调用此方法。 |
__getattribute__ |
此方法只影响实例。实例的所有属性的访问,第一个就调用此方法。此方法能完全控制属性的默认访问顺序。可以在此方法中做一些处理,然后手动抛出AttributeError 异常,这将继续调用__getattr__ 方法(如果有的话)。 |
__getattr__ VS __getattribute__
两者的执行时间点不同。
前者会在默认属性搜索顺序中未找到属性时,拦截异常,并执行。
后者会在第一时间执行,完全拦截默认属性搜索顺序。两者执行顺序如下:
__getattribute__
⟶ 实例属性的默认搜索顺序
⟶ __getattr__
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class A: def __init__(self, x, y):
self.__dict__['a'] = 7 self.__dict__['_d'] = {}
self.x = x self.y = y
def __getattr__(self, item): print('_in getattr:', item)
return self._d[item]
def __setattr__(self, key, value): print('_in setattr:', key, value)
self._d[key] = value
def __delattr__(self, item): print('_in delattr:', item) del self._d[item]
a = A(4, 5) print(a.__dict__) print(a.x) del a.x delattr(a, 'y') a.t = 123 print(a.__dict__) print('=' * 30)
class A: d = {} def __init__(self, x, y): self.x = x self.y = y
def __getattribute__(self, item): print(item, '~~~~~~~')
return object.__getattribute__(self, item)
a = A(3, 4) print('#' * 30) print(a.x, a.d)
|
如果上面提及的魔术方法同时存在,会怎么样呢?详见 Python中的属性搜索顺序
描述器的应用
用描述器实现ClassMethod、StaticMethod(非数据描述器的应用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class StaticMethod(object): def __init__(self, fn): self.fn = fn
def __get__(self, instance, owner): print('_in StaticMethod') print(self, instance, owner) return self.fn
class ClassMethod(object): def __init__(self, fn): self.fn = fn
def __get__(self, instance, owner): print('_in ClassMethod') print(self, instance, owner) return partial(self.fn, owner)
class A2(object): AGE = 20
def __init__(self, name, age): self.name = name self.__age = age
@StaticMethod # foo=StaticMethod(foo) 构建非数据描述器对象foo def foo(x): print('_in foo') return x
@ClassMethod # bar=ClassMethod(bar) 构建非数据描述器对象bar def bar(cls, x): print('_in bar') return cls.AGE, x
a = A2('Tom', 19)
print(a.foo(3)) print('=' * 30)
print(a.bar(4))
|
用描述器实现Property(数据描述器的应用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class Property(object): def __init__(self, fget, fset=None, fdel=None): self.fget = fget self.fset = fset self.fdel = fdel
def __get__(self, instance, owner): return self.fget(instance)
def __set__(self, instance, value): self.fset(instance, value)
def __delete__(self, instance): self.fdel(instance)
def setter(self, fset): self.fset = fset return self
def deleter(self, fdel): self.fdel = fdel return self
class A(object): def __init__(self): self.__age = 13
@Property def age(self): return self.__age
@age.setter def age(self, value): self.__age = value
@age.deleter def age(self): del self.__age
a = A() print(a.age) a.age = 17 print(a.age) del a.age
|