魔术方法是什么呢?顾名思义,魔术方法是一种特殊的方法,可以让自定义类具有某种魔法。魔术方法名开头结尾都包含两个下划线,例如 init 方法,负责对实例对象进行初始化。
下表将常用的魔术方法分门别类,你或多或少可能已有所了解:
合理应用魔术方法,让自定义类更具 Python 格调,更好地践行 Python 数据抽象以及设计哲学,是每个 Python 工程师必备的编程技巧。接下来,我们一起来考察几个典型的案例,以此抛砖引玉。
运算符重载
运算符,诸如加减乘除( +-*/
),处理逻辑由 add 等数值操作魔术方法控制。以加法运算符 +
为例,表达式 a + b
在 Python 内部是这样求值的:
- 如果 a 定义了魔术方法 add ,则调用 a.add(b) 进行求值;
- 如果 b 定义了魔术方法 radd ,则调用 b.radd(a) 进行求值;
因此,只需要提供相关魔术方法,非数值型对象也可以支持算术运算符。
举个例子, str 对象以加法进行对象拼接,以乘法进行对象重复,而除法却没有定义:
|
|
字符串切分是一个比较常见的操作,若能借助除法操作符来进行则方便许多。实现这个目标并不难,我们只需为 str 编写一个派生类,并实现魔术方法 truediv 即可:
|
|
在 Python 内部,除法操作 /
由 truediv 魔术方法处理。请注意,在老版本 Python 中,除法操作符由 div 魔术方法处理,名字略有差异。
就这么几行代码,我们的字符串类便支持通过除法操作进行字符串切分了:
|
|
数值型运算
结构稍微复杂一些的类型,也可以通过实现数值型魔术函数,让它具备数值类型的行为,支持常见的算术操作。
举个简单的例子,我们可以实现一个类来表示向量,x、 y 属性分别存储向量的坐标:
|
|
- add 魔术方法,实现向量加法;
- sub 魔术方法,实现向量减法;
- mul 魔术方法,实现向量数乘;
- truediv 魔术方法,实现向量数除;
- repr 魔术方法,实现向量表示;
这样一来,我们就可以通过常用算术运算符进行向量运算:
|
|
将自定义类与 Python 的运行哲学相融合,还可带来一些额外的收益——充分发挥 Python 的强大执行能力。
举个例子,我们可以借助现成的 sum 内建函数对多个向量进行求和,完全不需要任何额外的代码:
|
|
看,Python 语言的表达能力就是这样强大!只需对 Python 设计哲学以及相关运行时约定稍有了解,即可将这一切发挥得淋漓尽致!
属性描述符
上一小节,我们考察了属性描述符,知道它控制着属性查找的行为。属性描述符分为两种:
- 非数据描述符 :只实现了 get 方法;
- 数据描述符 :至少实现了 set 或者 delete 方法;
我们对非数据描述符已经有所了解:函数对象就是其中最典型的一个,它包含着类方法 self 参数绑定的全部秘密。然而,我们对数据描述符却一无所知!别急,我们将通过一个典型的例子,将这部分知识补齐。
对于对象 o ,其类型对象为 t 。如果 t 包含数据描述符属性 a ,那么属性设置操作 o.a = x
被 a.set 方法接管;同理,属性删除操作 del o.a
则被 a.delete 方法接管。
如果对象 o 属性空间也存在属性 a ,到底以谁为准呢?简而言之,Python 将照以下优先级逐一确定:
- 数据描述符:如果类型对象(含父类)定义了同名数据描述符属性,属性操作将被其接管;
- 对象属性:除了①,属性操作默认在属性空间中完成;
- 非数据描述符:属性访问时,如果①②均不成功,而类型对象(含父类)定义了同名非数据描述符,属性访问将被其接管;
因此,数据描述符优先级最高,对象属性空间次之,非数据描述符最低。下图是一个典型的例子:
- 对于属性 a ,由于类型对象 t 属性空间定义了数据描述符,将屏蔽实例对象 o 属性空间中的定义;
- 对于属性 b ,由于类型对象 t 属性空间定义的只是非数据描述符,仍以实例对象 o 属性空间定义的为准;
- 对于属性 c ,由于实例对象 o 属性空间未定义,属性访问将以类型对象 t 属性空间定义的非数据描述符为准;
- 对于属性 c ,由于类型对象 t 属性空间定义的只是非数据描述符,属性设置、删除仍以实例对象 o 属性空间为准;
那么,数据描述符到底有什么用处呢?点击“阅读原文”,获取更多细节!
【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: