魔术方法
魔术方法 ( magic methods ) 是 python 的一种语法,允许在类的定义中使用,一般格式是 __xx__
,它能对类进行功能上的额外增加,同时又非常方便,并且我们不得不去研究它。
为什么说不得不研究?比如我们知道使用 str()
可以将一个对象转换成 string 输出,那么 repr()
也能将一个对象转换成 string 输出,它们的区别是什么?前者调用了 __str__
后者调用了 __repr__
,它们的魔术方法不同…
魔术方法怎么用呢?比如说我们要定义一个人 ,同时要有姓名、年龄,还要允许根据姓名和年龄进行排序:
1 | #coding:utf8 |
这里用了三个魔术方法: __init__
初始化, __str__
字符串格式, __lt__
小于的含义。
或者说一个非常理性的需求,想对默认的 dict 进行修改,让它可以完成 dict[1][1] = x
这样的操作,当然默认是不行的,我们只能依靠 dict.update({'1':{'1':x}})
完成,非常绕口。想简便完成这个任务,首先我们无法确定 dict 中是否存在某个值,可以通过 __getitem__
函数重新定义:
1 | class MyDict(dict): |
所以魔术方法给了我们非常多的便利。
用于创建删除实例
当一个实例被创建,最先被使用的方法是 __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 | urlunquote = functools.partial(urlunquote, 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 | def __setattr__(self, name. value): |
容器中的魔术方法
名称 | 参数 | 含义 |
---|---|---|
__len__ |
self | 返回容器长度 |
__getitem__ |
self, key | 返回某一项,必须存在 |
__setitem__ |
self, key | 设置某一项,它将先使用 __getitem__ |
__iter__ |
self, key | 返回迭代器 |
__reversed__ |
self | 反转 |
__contains__ |
self, item | 是否存在某一项 |
__missing__ |
self ,key | 不存在键时的行为 |
到这里,基本该使用的魔术方法已经列举了,其实还剩下很多,甚至无穷无尽,每一个包的创建,我们也就能创建一个又一个魔术方法,所以学习无止境,等待我们的探索。
continue…