用于控制Python对象行为的魔法方法
什么是魔法方法
魔法方法,或称特殊方法,是Python类中用于定义特殊行为的内置函数,包括运算符重载、对象输出方式改变、像容器一样运作等,帮助更丰富地定义对象。主要有以下特点:
- 可定义四则运算等基本行为
- 由两个下划线(_)包围定义
- 与普通函数不同,不显式执行
class Example():
def __init__(self, value):
self.value = value
魔法方法的典型例子 __init__() 在对象创建时被调用进行初始化。如果先用其他语言接触面向对象,可能会觉得Python这种在 __init__() 中定义内部变量的结构不太方便,但理解了Python中使用魔法方法的语境后,就能更清爽地接受”Python是想用内置方法解决这个问题”。
魔法方法的适用范围比我们想象的更广泛。例如,基础四则运算中的加法,表面上用 + 运算符表示,但实际上调用的是对象内部的 __add__() 函数。
class Example():
def __init__(self, value):
self.value = value
def __add__(self, x):
return self.value + x.value
ex1 = Example(10)
ex2 = Example(20)
print(ex1 + ex2) # 返回 30
因此,如果需要,也可以实现如下——使用 + 运算符但实际不执行加法运算的操作。
def __add__(self, x):
return "您是想进行加法运算吗?"
包括四则运算在内,可以定义如下多种魔法方法,此外还可以定义各种其他功能。
| 魔法方法 | 运算符/功能 |
|---|---|
__add__ | + |
__sub__ | - |
__mul__ | * |
__truediv__ | / |
__gt__ | > |
__lt__ | < |
__le__ | <= |
__ge__ | >= |
__eq__ | == |
__new__ | 对象创建 |
__del__ | 对象删除 |
两个特别事项
创建内部文档
虽然不是魔法方法,但值得一并了解的是 __doc__。__doc__ 通过类或方法定义部分下方的长注释(文档字符串)为对象和内置方法创建内部文档。
class Example():
'''This is a doc'''
# ...
可以记录该类或方法的开发用途、使用方法等模块的简要信息。可以通过 print(Example.__doc__) 或 help() 方法访问,更详细的编写规范可参考Python增强提案。
查看魔法方法
必要时,可以通过 dir() 查看特定对象中定义的所有魔法方法。例如,用 dir(10) 查看整数的魔法方法时,可以看到以下结果。
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__',
'__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__',
'__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__',
'__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__',
'__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__',
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__',
'__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag',
'is_integer', 'numerator', 'real', 'to_bytes']
利用这一特性,可以通过 dir() 掌握外部库等外部对象支持的魔法方法种类,或者像 if '__add__' in dir(obj) 这样,根据对象是否支持特定运算来区分行为。
魔法方法应用示例
可以用于实现简易委托。在Python中,委托链本身通过列表添加函数更为简洁和推荐,但个人一直感到某种说不出的不便,于是找到了以下方法。
class RaiseEvent():
def __init__(self):
self._handlers = []
def __iadd__(self, function):
self._handlers.append(function)
return self
def __call__(self, *args, **kwargs):
for function in self._handlers:
function(*args, **kwargs)
编写上述类后,可以通过 += 运算符添加函数,像C#一样以 raise_event += function 熟悉的形式使用。当项目规模较大时,预先编写这种自定义类,从长远来看似乎更有可读性优势。