描述器
描述器
描述器
描述器协议
-
__get__(self, instance, owner) -
__set__(self, instance, value) -
__delete__(self, instance)
这是所有的「描述器」的方法.
一个对象具有其中 任一 方法就会成为「描述器」.
如果一个对象同时定义了 __get__() 和 __set__() 方法,则叫做「资料描述器」(data descriptor)
只定义了 __get__() 方法的「描述器」叫做「非资料描述器」(non-data descriptor)
应用
「描述器」正是属性,实例方法,静态方法,类方法和 super 的背后的实现机制.
「描述器」通常是那些使用到装饰器或元类的大型框架中的一个组件.
示例
class Integer:
''' 描述器:设置值的时候,必须是 int 类型
'''
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
else:
return instance.__dict__[slef.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
class Point:
x = Integer('x')
y = Integer('y')
def __init__(self, x, y):
self.x = x
self.y = y
In [5]: p = Point(2, 3)
In [6]: p.x
Out[6]: 2
In [7]: p.y = 5
In [8]: p.x = 2.3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-0a3d61583921> in <module>()
----> 1 p.x = 2.3
<ipython-input-4-b8f8e8c000be> in __set__(self, instance, value)
13 def __set__(self, instance, value):
14 if not isinstance(value, int):
---> 15 raise TypeError('Expected an int')
16 instance.__dict__[self.name] = value
17
TypeError: Expected an int
属性查找
对象属性查找
假设,d = Demo(),则 d.attr 的时候,会调用 __getattribute__()
如果类定义了 __getattr__(),那么在 __getattribute__() 抛出 AttributeError 的时候,就会调用 __getattr__()
对象属性的查找顺序:
-
查找
Demo及其基类的__dict__是否存在属性attr,如果存在并且该属性attr是「资料描述器」,则返回Demo.__dict__['attr'].__get__(d, Demo).如果是在
Demo的基类Base中查找到attr属性并且该属性是「资料描述器」,则返回Base.__dict__['attr'].__get__(d, Demo). -
查找
d.__dict__是否存在属性attr,如果存在,则直接返回d.__dict__['attr'] -
查找
Demo及其基类的__dict__是否存在属性attr,如果存在,是否是一个「非资料描述器」- 是,则返回
Demo.__dict__['attr'].__get__(d, Demo) - 否,则返回
Demo.__dict__['attr']
如果是在
Demo的基类查找到的,则返回相应的基类属性 - 是,则返回
-
若
Demo有__getattr__(),则调用__getattr__() -
抛出
AttributeError
示例
class D1:
def __get__(self, ins, owner):
print('D1资料描述器get, ins: %s, owner: %s' % (ins, owner))
def __set__(self, ins, value):
print('D1资料描述器set')
class D2:
def __get__(self, ins, owner):
print('非资料描述器get, ins: %s, owner: %s' % (ins, owner))
class Demo:
x = D1()
y = D2()
z = 1
n = 2
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
In [21]: d = Demo(1, 2, 3) # 因为 x 是「资料描述器」,self.x = x 触发 D1.__set__()
D1资料描述器set
In [22]: d.x # Demo.x 是「资料描述器」,调用的是 Demo.__dict__['x'].__get__(d, Demo)
D1资料描述器get, ins: <__main__.Demo object at 0x7fa3d448a978>, owner: <class '__main__.Demo'>
In [23]: d.y # Demo.y 是「非资料描述器」,调用的是 d.__dict__['y']
Out[23]: 2
In [25]: d.z # Demo.z 不是描述器,调用的是 d.__dict__['z']
Out[25]: 3
In [28]: d.n
Out[28]: 2
类属性查找
类同样也是一种对象,类是「元类」(metaclass)的实例,因此类属性的查找顺序基本跟对象属性相同.
详细了解「元类」,参考:深刻理解Python中的元类
假设,Base 是 Demo 的一个「元类」
class Demo(object, metaclass=Base):
pass
则 Demo.attr的时候,类属性的查找顺序:
-
查找
Base及其基类的__dict__是否存在属性attr,存在并且是「资料描述器」,则返回Base.__dict__['attr'].__get__(Demo, Base) -
查找
Demo.__dict__及其基类的__dict__是否存在attr,若存在,是否是一个描述器(不管是「资料描述器」还是「非资料描述器」):- 是,则返回
Demo.__dict__['attr'].__get__(None, Demo) - 否,则返回
Demo.__dict__['attr']
注意这里的
基类是指Demo继承的类,同「元类」是完全不同的. - 是,则返回
-
查找
Base及其基类的__dict__是否存在attr,如果存在,是否是一个「非资料描述器」- 是,则返回
Base.__dict__['attr'].__get__(Demo, Base) - 否,则返回
Base.__dict__['attr']
- 是,则返回
-
若
Base有__getattr__(),则Base.__getattr__('attr') -
抛出
AttributeError
示例
class D1:
def __get__(self, ins, owner):
print('D1资料描述器get, ins: %s, owner: %s' % (ins, owner))
def __set__(self, ins, value):
print('D1资料描述器set')
class D2:
def __get__(self, ins, owner):
print('非资料描述器get, ins: %s, owner: %s' % (ins, owner))
class D3:
def __get__(self, ins, owner):
print('D3资料描述器get, ins: %s, owner: %s' % (ins, owner))
def __set__(self, ins, value):
print('D3资料描述器set')
class Base(type):
x = D1()
y = D2()
z = 0
m = 1
def __getattr__(self, *args, **kwargs):
print('元类的 getattr')
class C:
x = D3()
y = 1
z = 1
class Demo(C, metaclass=Base):
z = 3
In [12]: Demo.x # 在元类 Base 查找到 x 并且是一个「资料描述器」,而不是在父类 C
D1资料描述器get, ins: <class '__main__.Demo'>, owner: <class '__main__.Base'>
In [13]: Demo.y # 在父类 C 找到 y,而元类 Base 的 y 只是一个「非资料描述器」
Out[13]: 1
In [14]: Demo.z # 在 Demo 类 找到 z,覆盖了父类 C 的 z,而元类 Base 的 z 不是描述器
Out[14]: 3
In [17]: Demo.m # 在元类 Base 找到 m
Out[17]: 1
In [19]: Demo.i # 找不到i,在元类 Base 的 __getattr__ 处理
元类的 getattr