魔术方法

资料柜:
A Guide to Python’s Magic Methods

魔术方法 ( magic methods ) 是 python 的一种语法,允许在类的定义中使用,一般格式是 __xx__ ,它能对类进行功能上的额外增加,同时又非常方便,并且我们不得不去研究它。

为什么说不得不研究?比如我们知道使用 str() 可以将一个对象转换成 string 输出,那么 repr() 也能将一个对象转换成 string 输出,它们的区别是什么?前者调用了 __str__ 后者调用了 __repr__ ,它们的魔术方法不同…

魔术方法怎么用呢?比如说我们要定义一个人 ,同时要有姓名、年龄,还要允许根据姓名和年龄进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#coding:utf8

class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
return

def __str__(self):
return self.name + ":" + str(self.age)

def __lt__(self, other):
return self.name < other.name \
if self.name != other.name \
else self.age < other.age


print("\t".join(
[str(item)for item in sorted([People("abc", 18),
People("qwd", 19),
People("wds", 12),
People("awd", 17)])])
)

这里用了三个魔术方法: __init__ 初始化, __str__ 字符串格式, __lt__ 小于的含义。

或者说一个非常理性的需求,想对默认的 dict 进行修改,让它可以完成 dict[1][1] = x 这样的操作,当然默认是不行的,我们只能依靠 dict.update({'1':{'1':x}}) 完成,非常绕口。想简便完成这个任务,首先我们无法确定 dict 中是否存在某个值,可以通过 __getitem__ 函数重新定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyDict(dict):

def __getitem__(self, item):
if item not in self:
temp = MyDict()
self[item]=temp
return temp
return self[item]

test = MyDict()
test[0] = 'test'
test[1][2] = 'test1'
test[3][4][5] = 'test2'
print(test)

所以魔术方法给了我们非常多的便利。

用于创建删除实例

当一个实例被创建,最先被使用的方法是 __new__(cls, *args, **kwargs) ,它的第一个参数是类,后面的可变参数将传递给 __init__ 使用,所以其追主要的目的是在 __init__ 前做准备,而值得一提的是,这一切都是由元类的 __call__ 控制的。

接下来是实例的初始化方法,使用的方法是 __init__(self) ,它的第一个参数是实例自身。

当对象的使命结束,在删除时调用的方法是 __del__(self) ,它相当于程序的析构器,但是它不能在解释器退出以后执行,所以它只在特定的环境下有作用,不是一个优美的编程习惯,对于 cpython 还是仰仗 gc 模块。

比较的魔术方法

在 python 语言中我们可能需要这样来进行等于的判断:

1
if instance.equals(other_instance) : pass

这样的操作可能会让产生困扰,于是有了 __eq__ 方法,当实现其之后,可以这样:

1
if instance == other_instance : pass

这是比较的魔术方法的一个小例子。

最完善的比较是 __cmp__ 它实现了所有的比较符号,包括了 <,==,>,!=,etc. ,但是它需要对每一个可能进行定义,比如 self < other 返回0, self = other 返回负数, self > other 返回正数。所以这个方法是让你保持所有状态明白的一个好方法。

下面列举其他比较用魔术方法。

名称 参数 含义
__eq__ self, other 等号的行为
__ne__ self, other 不等号的行为
__lt__ self, other 小于号的行为
__gt__ self, other 大于等于号的行为

这里值得一提的是 functools 模块,该模块大大降低了开发成本,其中的 functool.partial 通过包装类的手法,允许我们重新定义函数签名,什么意思呢?

1
2
3
4
urlunquote = functools.partial(urlunquote, encoding='latin1')
# 上面定义以后,下面的调用等价
# urlunquote(args, *kargs)
# urlunquote(args, *kargs, encoding='latin1')

再比如说 functool.update_wrapper ,该函数可以补充封装函数的 __name__、__module__、__doc__和 __dict__ 等信息,便于 debug。

functools.wraps 可以作为 functool.update_wrapper 的简易写法,以装饰器的方式使用。

此外还有 reduce 作为兼容 py3 , cmp_to_key 小于负数大于整数等于为0, total_ordering 在实现 __eq__ 和其他一个比较的前提下,自动实现其他比较的函数。

一元操作符

顾名思义,一个操作位的一元操作,好比绝对值等。

名称 参数 含义
__neg__ self -
__pos__ self +
__abs__ self abs()
__invert__ self ~
__complex__ self 转换成复数
__int__ self 转换成整型
__long__ self 转换成长整型
__float__ self 转换成浮点型
__oct__ self 转换成八进制
__hex__ self 转换成十六进制
__round__ self, n 四舍五入
__floor__ self 向下取整
__ceil__ self 向上取整
__trunc__ self 距离 0 最近的整数
__coerce__ self 将一个列表转换成相同类型

二元操作符

好比加法。

名称 参数 含义
__add__ self, other 加法
__sub__ self, other 减法
__mul__ self, other 乘法
__floordiv__ self, other 整除
__div__ self, other 除法
__truediv__ self, other 真除,但结果和除没有区别
__mod__ self, other 取模
__divmod__ self, other 如果两个参数 a , b 都是整数,那么会采用整数除法,结果相当于 (a//b, a % b) 。如果 a 或 b 是浮点数,相当于 (math.floor(a/b), a%b)
__pow__ self, other 指数 **
__lshift__ self, other 左移
__rshift__ self, other 右移
__and__ self, other 按位 &
__or__ self, other 按位 ` `
__xor__ self, other 按位 ^

省略反运算和增量赋值,没有区别且个人觉得没有太大意义,可以在使用时再查看。

类的表示

什么叫类的表示?类在程序中的输出以及行为。比如输出 string,下面的方法在特定情况下一般只修改极个别,并且可以用 obj.method() 调用。其中的 __sizeof__ 使用的是 getsizeof()

名称 参数 含义
__str__ self 人读的输出
__repr__ self 机器读的输出
__unicode__ self unicode 字符串
__hash__ self hash 值
__nonzero__ self 将类转换成布尔值
__dir__ self 属性列表
__format__ self 格式化输出
__len__ self 输出长度
__sizeof__ self 输出内存

属性访问

名称 参数 含义
__getattr__ self, name 获取属性,必须存在
__setattr__ self, name 设置属性,无论是否存在
__delattr__ self, name 删除属性
__getattribute__ self, name 获取属性,无论是否存在

PS: 在 setattr 中可能出现无限循环

1
2
3
4
5
6
7
8
9
def __setattr__(self, name. value):
self.name = value
# 因为每次属性幅值都要调用 __setattr__(),所以这里的实现会导致递归
# 这里的调用实际上是 self.__setattr('name', value)。因为这个方法一直
# 在调用自己,因此递归将持续进行,直到程序崩溃

def __setattr__(self, name, value):
self.__dict__[name] = value
# 使用 __dict__ 进行赋值

容器中的魔术方法

名称 参数 含义
__len__ self 返回容器长度
__getitem__ self, key 返回某一项,必须存在
__setitem__ self, key 设置某一项,它将先使用 __getitem__
__iter__ self, key 返回迭代器
__reversed__ self 反转
__contains__ self, item 是否存在某一项
__missing__ self ,key 不存在键时的行为

到这里,基本该使用的魔术方法已经列举了,其实还剩下很多,甚至无穷无尽,每一个包的创建,我们也就能创建一个又一个魔术方法,所以学习无止境,等待我们的探索。

continue…