在阅读一些开源Python库的源码时,经常会看到在某个类的成员函数前,有类似于@staticmethod或@classmethod或@property的语法糖。本质上,它们都是函数装饰器,只不过通常被用来修饰类成员函数而已。
本笔记旨在说明这些语法糖的用途,关于普通函数装饰器语法的解释,可以参考这篇笔记。
在解释这些装饰器函数前,先来分析下普通成员函数。1. 类的普通成员函数
对于Python的类,其普通类成员函数的第一个参数默认为当前的类实例,通常的定义形式示例:
class C:
def __init__(self): ## NOTICE: 不传入参数时创建实例会报错;"self"只是约定俗成的参数名而已,非语法的硬性规定
pass
我们可以通过print C.__init__查看函数__init__():>>> class C():
... def __init__(self):
... pass
...
>>> print C.__init__
<unbound method C.__init__> ## __init__()是类C的unbound method,即它此时未bound到任何类实例上
>>> c = C()
>>> print c.__init__
<bound method C.__init__ of <__main__.C instance at 0x7fb4346d38c0>> ## 当类实例创建后,__init__()变成实例的bound method
>>> print C.__init__.__get__
<method-wrapper '__get__' of instancemethod object at 0x7fb434772c80> ## 类成员函数默认实现了__get__()方法
总之,对于类的普通成员函数来说,在创建类的具体实例前,它们是unbound method,通过类名调用时(如 C.fn()),会报错;类实例创建
后,它们都是实例的bound method,只能通过实例来调用(inst = C(); inst.fn())。关于unbound method函数调用报错的原因,与Python底层实现时对函数名的查找规则有关。从上面示例代码可看到,__init__()函数默 认实现了__get__()方法,而根据Python底层规则,当某个对象(Python中万物皆对象)实现了_get__()/__set__()/__delete__()这3个 method中任何一个时,它就成了一个支持descriptor protocal的descriptor。当调用c.__init__时(当然,这个是Python解释器帮我们调 用的,但这并不改变__init__实际上是个普通类函数的事实),根据descriptor invoking规则,其将被转化为type(c).__dict__ ['__init__'].__get__(c, type(c))的形式,可见,实际上发生的调用类似于C.__init__(c),即类C的实例c会被当作第1个参数传给普通类 成员函数。所以,在定义类普通成员函数时,至少需要有self参数,且类的普通成员函数必须通过类实例来调用,而不能通过类名直接调用。
关于上面提到的Descriptor Protocol及其对obj attribute查找规则的影响,强烈建议读懂这篇文档Descriptor HowTo Guide 。不夸张的说,理解Descriptor对我们理解Python代码的底层行为有巨大帮助。