Python 黑魔法—描述器(descriptor)
Python黑魔法,前面已经介绍了两个魔法,装饰器和迭代器,通常还有个生成器。生成器固然也是一个很优雅的魔法。生成器更像是函数的行为。而连接类行为和函数行为的时候,还有一个描述器魔法,也称之为描述符。
我们不止一次说过,Python的优雅,很大程度在于如何设计成优雅的API。黑魔法则是一大利器。或者说Python的优雅很大程度上是建立在这些魔法巧技基础上。
何谓描述器
当定义迭代器的时候,描述是实现迭代协议的对象,即实现__iter__
方法的对象。同理,所谓描述器,即实现了描述符协议,即__get__
, __set__
, 和 __delete__
方法的对象。
单看定义,还是比较抽象的。talk is cheap。看代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class WebFramework(object): def __init__(self, name='Flask'): self.name = name def __get__(self, instance, owner): return self.name def __set__(self, instance, value): self.name = value class PythonSite(object): webframework = WebFramework() In [1]: PythonSite.webframework Out[1]: 'Flask' In [2]: PythonSite.webframework = 'Tornado' In [3]: PythonSite.webframework Out[3]: 'Tornado' |
定义了一个类WebFramework,它实现了描述符协议__get__
和__set__
,该对象(类也是对象,一切都是对象)即成为了一个描述器。同时实现__get__
和__set__
的称之为资料描述器(data descriptor)。仅仅实现__get__
的则为非描述器。两者的差别是相对于实例的字典的优先级。
如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。
描述器的调用
对于这类魔法,其调用方法往往不是直接使用的。例如装饰器需要用 @ 符号调用。迭代器通常在迭代过程,或者使用 next 方法调用。描述器则比较简单,对象属性的时候会调用。
1 2 3 4 |
In [15]: webframework = WebFramework() In [16]: webframework.__get__(webframework, WebFramework) Out[16]: 'Flask' |
描述器与对象属性
OOP的理论中,类的成员变量包括属性和方法。那么在Python里什么是属性?修改上面的PythonSite类如下:
1 2 3 4 5 6 7 8 |
class PythonSite(object): webframework = WebFramework() version = 0.01 def __init__(self, site): self.site = site |
这里增加了一个version的类属性,以及一个实例属性site。分别查看一下类和实例对象的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
In [1]: pysite = PythonSite('ghost') In [2]: vars(PythonSite).items() Out[2]: [('__module__', '__main__'), ('version', 0.01), ('__dict__', <attribute '__dict__' of 'PythonSite' objects>), ('webframework', <__main__.WebFramework at 0x10d55be90>), ('__weakref__', <attribute '__weakref__' of 'PythonSite' objects>), ('__doc__', None), ('__init__', <function __main__.__init__>)] In [3]: vars(pysite) Out[3]: {'site': 'ghost'} In [4]: PythonSite.__dict__ Out[4]: <dictproxy {'__dict__': <attribute '__dict__' of 'PythonSite' objects>, '__doc__': None, '__init__': <function __main__.__init__>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'PythonSite' objects>, 'version': 0.01, 'webframework': <__main__.WebFramework at 0x10d55be90>}> |
vars方法用于查看对象的属性,等价于对象的__dict__
内容。从上面 符。
我们不止一次说过,Python的优雅,很大程度在于如何设计成优雅的API。黑魔法则是一大利器。或者说Python的优雅很大程度上是建立在这些魔法巧技基础上。
何谓描述器
当定义迭代器的时候,描述是实现迭代协议的对象,即实现__iter__
方法的对象。同理,所谓描述器,即实现了描述符协议,即__get__
, __set__
, 和 __delete__
方法的对象。
单看定义,还是比较抽象的。talk is cheap。看代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class WebFramework(object): def __init__(self, name='Flask'): self.name = name def __get__(self, instance, owner): return self.name def __set__(self, instance, value): self.name = value class PythonSite(object): webframework = WebFramework() In [1]: PythonSite.webframework Out[1]: 'Flask' In [2]: PythonSite.webframework = 'Tornado' In [3]: PythonSite.webframework Out[3]: 'Tornado' |
定义了一个类WebFramework,它实现了描述符协议__get__
和__set__
,该对象(类也是对象,一切都是对象)即成为了一个描述器。同时实现__get__
和__set__
的称之为资料描述器(data descriptor)。仅仅实现__get__
的则为非描述器。两者的差别是相对于实例的字典的优先级。
如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。
描述器的调用
对于这类魔法,其调用方法往往不是直接使用的。例如装饰器需要用 @ 符号调用。迭代器通常在迭代过程,或者使用 next 方法调用。描述器则比较简单,对象属性的时候会调用。
1 2 3 4 |
In [15]: webframework = WebFramework() In [16]: webframework.__get__(webframework, WebFramework) Out[16]: 'Flask' |
描述器与对象属性
OOP的理论中,类的成员变量包括属性和方法。那么在Python里什么是属性?修改上面的PythonSite类如下:
1 2 3 4 5 6 7 8 |
class PythonSite(object): webframework = WebFramework() version = 0.01 def __init__(self, site): self.site = site |
这里增加了一个version的类属性,以及一个实例属性site。分别查看一下类和实例对象的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
In [1]: pysite = PythonSite('ghost') In [2]: vars(PythonSite).items() Out[2]: [('__module__', '__main__'), ('version', 0.01), ('__dict__', <attribute '__dict__' of 'PythonSite' objects>), ('webframework', <__main__.WebFramework at 0x10d55be90>), ('__weakref__', <attribute '__weakref__' of 'PythonSite' objects>), ('__doc__', None), ('__init__', <function __main__.__init__>)] In [3]: vars(pysite) Out[3]: {'site': 'ghost'} In [4]: PythonSite.__dict__ Out[4]: <dictproxy {'__dict__': <attribute '__dict__' of 'PythonSite' objects>, '__doc__': None, '__init__': <function __main__.__init__>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'PythonSite' objects>, 'version': 0.01, 'webframework': <__main__.WebFramework at 0x10d55be90>}> |
vars方法用于查看对象的属性,等价于对象的__dict__
内容。从上面 ayon-5812b20437973263109958-21">