Python中的魔术方法

本文将详细描述Python中的魔术方法,这些方法是Python中很有意思也很重要的一部分内容,掌握它们可以让你对Python的面向对象特性有更深的理解,也会让你的Python技能更上一层楼。

Python的魔术方法分类

我们会从几个大类来讨论Python的魔术方法:

  1. 对象的构造和初始化
  2. 用于比较和运算符的魔术方法
  3. 打印对象的魔术方法
  4. 控制对象属性访问的魔术方法
  5. 可迭代对象和容器的魔术方法
  6. 反射的魔术方法
  7. 可调用的对象的魔术方法
  8. 会话管理的魔术方法
  9. 创建对象描述器的魔术方法
  10. 拷贝的魔术方法
  11. 对象的序列化和反序列化的魔术方法

1.对象的构造和初始化

方法名 含义
__new__(cls, [,…]) 创建新的类实例
__init__(self, [,…]) 初始化类实例
__del__(self) 在对象被垃圾回收时调用

来看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ProgramLauguage(object):

def __init__(self, name):
print("in __init__ {}".format(self))
self.name = name

def __new__(cls, name, *args, **kwargs):
print("in __new__ {}".format(cls))
return object.__new__(cls, *args, **kwargs)

def __del__(self):
print("in __del__ {}".format(self.name))
del self

d1 = dict()
d2 = dict()
d3 = dict()

d1["ruby"] = ProgramLauguage("Ruby")
d2["ruby"] = d1["ruby"]
d3["ruby"] = d1["ruby"]

print('del d1["ruby"]')
del d1["ruby"]

print('del d2["ruby"]')
del d2["ruby"]

print('del d3["ruby"]')
del d3["ruby"]

python = ProgramLauguage("Python")

运行上面的代码,会打印:

1
2
3
4
5
6
7
8
9
in __new__ <class '__main__.ProgramLauguage'>
in __init__ <__main__.ProgramLauguage object at 0x10daf3080>
del d1["ruby"]
del d2["ruby"]
del d3["ruby"]
in __del__ Ruby
in __new__ <class '__main__.ProgramLauguage'>
in __init__ <__main__.ProgramLauguage object at 0x10daf3080>
in __del__ Python

从上面的输出可以看出,在实例化一个类时,将首先调用该类的__new__方法,在__new__方法中创建一个新对象,然后把实例化类时传入的参数原封不动地传递给__init__方法。在__init__方法内部,初始化这个实例。需要注意的是,__new__需要返回这个新创建的对象,而__init__不需要返回任何东西。因为__new__负责生成新实例,__init__负责初始化这个新实例。需要注意的是,如果__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行。

比较会让人迷惑的是__del__方法,乍一看会以为在调用del obj时调用对象的__del__,其实不是这样的,注意观察d1d2d3,这三个字典引用了同一个ProgramLauguage实例,在依次对这三个字典用引用的同一个实例调用del时,只有在del d3["ruby"]之后才打印了in __del__ Ruby。这就说明并不是del obj这个操作触发了__del__,准确地说,__del__会在对象被垃圾回收的时候被调用。因此我们可以把一些对象内部的清理操作放在__del__中。

2.用于比较和运算符的魔术方法

用于比较的魔术方法

方法名 含义
__cmp__(self, other) 定义了大于、小于和等于的方法
__eq__(self, other) 定义了等号的行为, ==
__ne__(self, other) 定义了不等号的行为, !=
__lt__(self, other) 定义了小于号的行为, <
__gt__(self, other) 定义了大于号的行为, >
__le__(self, other) 定义了小于等于号的行为, <=
__ge__(self, other) 定义了大于等于号的行为, >=

来看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Cat(object):

def __init__(self, name, age, weight):
self.name = name
self.age = age
self.weight = weight

def __cmp__(self, other):
if self.age < s.age:
return -1
elif self.age > s.age:
return 1
else:
return 0

def __eq__(self, other):
return self.name == other.name and self.age == other.age and self.weight == other.weight

def __ne__(self, other):
return self.name != other.name or self.age != other.age or self.weight != other.weight

def __lt__(self, other):
return self.age < other.age

def __gt__(self, other):
return self.age > other.age

def __le__(self, other):
return self.age <= other.age

def __ge__(self, other):
return self.age >= other.age

cat1 = Cat('Mio', 1, 4)
cat2 = Cat('Mio', 1, 4)
cat3 = Cat('Lam', 2, 6)

print(cat1 == cat2) # True
print(cat2 == cat3) # False
print(cat2 != cat3) # True
print(cat1 < cat3) # True
print(cat1 <= cat3) # True
print(cat3 > cat1) # True
print(cat3 >= cat1) # True

需要说明的是,__cmp__定义了大于、小于和等于的方法,当前对象大于、小于和等于另一个对象时,它的返回值分别大于0、小于0和等于0。其他几个用于比较的魔术方法的含义就如开头所述,很好理解。

用于运算符的魔术方法

一元操作符和函数魔术方法

方法名 含义
__pos__(self) 实现+号的特性
__neg__(self) 实现-号的特性
__abs__(self) 实现内置 abs() 函数的特性
__invert__(self) 实现~符号的特性(取反)

看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Person(object):

def __init__(self, name, age):
self.name = name
self.age = age

def __pos__(self):
self.age += 1

def __neg__(self):
self.age -= 1

def __abs__(self):
return abs(self.age)

def __invert__(self):
return ~self.age

p = Person('Jack', 20)
print(p.age) # 20
+p
print(p.age) # 21
-p
print(p.age) # 20
print(abs(p)) # 20
print(~p) # -21

这段代码很简单,就不过多解释了。

普通算数操作符魔术方法

方法名 含义
__add__(self, other) 实现加法
__sub__(self, other) 实现减法
__mul__(self, other) 实现乘法
__floordiv__(self, other) 实现地板除法(//),即整数除法
__div__(self, other) 实现/符号的除法,只在py2生效
__truediv__(self, other) 实现真除法,用于py3
__mod__(self, other) 实现取模运算
__divmod___(self, other) 实现内置的divmod()函数
__pow__(self, other) 实现**指数运算
__lshift__(self, other) 实现使用 << 的按位左移位
__rshift__(self, other) 实现使用 >> 的按位右移位
__and__(self, other) 实现使用 & 的按位与
__or__(self, other) 实现使用 \ 的按位或
__xor__(self, other) 实现使用 ^ 的按位异或

我们来实现一个MyNumber类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
class MyNumber(object):

def __init__(self, num):
self.num = num

def __add__(self, other):
"""
MyNumber(x) + MyNumber(y)
"""
return self.__class__(self.num + other.num)

def __sub__(self, other):
"""
MyNumber(x) - MyNumber(y)
"""
return self.__class__(self.num - other.num)

def __mul__(self, other):
"""
MyNumber(x) * MyNumber(y)
"""
return self.__class__(self.num * other.num)

def __floordiv__(self, other):
"""
MyNumber(x) // MyNumber(y)
"""
return self.__class__(self.num // other.num)

def __div__(self, other):
"""
[in py2] MyNumber(x) / MyNumber(y)
"""
return self.__class__(self.num / other.num)

def __truediv__(self, other):
"""
[in py3] MyNumber(x) / MyNumber(y)
"""
return self.__class__(self.num / other.num)

def __mod__(self, other):
"""
MyNumber(x) % MyNumber(y)
"""
return self.__class__(self.num % other.num)

def __pow__(self, other):
"""
MyNumber(x) ** MyNumber(y)
"""
return self.__class__(self.num ** other.num)

def __lshift__(self, other):
"""
MyNumber(x) << MyNumber(y)
"""
return self.__class__(self.num << other.num)

def __rshift__(self, other):
"""
MyNumber(x) >> MyNumber(y)
"""
return self.__class__(self.num >> other.num)

def __and__(self, other):
"""
MyNumber(x) & MyNumber(y)
"""
return self.__class__(self.num & other.num)

def __or__(self, other):
"""
MyNumber(x) | MyNumber(y)
"""
return self.__class__(self.num | other.num)

def __xor__(self, other):
"""
MyNumber(x) ^ MyNumber(y)
"""
return self.__class__(self.num ^ other.num)

num1 = MyNumber(2)
num2 = MyNumber(3)

num3 = num1 + num2
num4 = num2 - num1
num5 = num1 * num2
num7 = num2 // num1
num8 = num2 / num1
num9 = num2 % num1
num10 = num2 ** num1
num11 = num2 << num1
num12 = num2 >> num1
num13 = num2 & num1
num14 = num2 | num1
num15 = num2 ^ num1

print(num3.num) # 5 (2+3)
print(num4.num) # 1 (3-2)
print(num5.num) # 6 (2*3)
print(num7.num) # 1 (3//2)
print(num8.num) # 1.5 (3/2)
print(num9.num) # 1 (3%2)
print(num10.num) # 9 (3**2)
print(num11.num) # 12 (3<<2)
print(num12.num) # 0 (3>>2)
print(num13.num) # 2 (3&2)
print(num14.num) # 3 (3|2)
print(num15.num) # 1 (3^2)

这部分代码也相对简单,就不解释了。

另外,普通算数操作符魔术方法均有相对应的反运算符魔术方法,即把两个操作数的位置对调,它们对应的反运算符魔术方法就是在方法名前__后加上r,比如__add__的反运算符魔术方法就是__radd__,其他的以此类推。

增量赋值魔术方法

方法名 含义
__iadd__(self, other) 实现赋值加法 +=
__isub__(self, other) 实现赋值减法 -=
__mul__(self, other) 实现赋值乘法 *=
__ifloordiv__(self, other) 实现赋值地板除法(//=),即整数除法
__idiv__(self, other) 实现/符号的赋值除法 /=
__itruediv__(self, other) 实现赋值真除法,需要from future import division
__imod__(self, other) 实现赋值取模运算 %=
__pow__(self, other) 实现指数赋值运算 **=
__ilshift__(self, other) 实现使用 <<= 的赋值按位左移位
__irshift__(self, other) 实现使用 >>= 的赋值按位右移位
__iand__(self, other) 实现使用 &= 的赋值按位与赋值
__ior__(self, other) 实现使用 \ = 的赋值按位或
__ixor__(self, other) 实现使用 ^= 的赋值按位异或

这部分魔术方法的示例代码和上面的差不多,只是把相应的运算改成赋值运算而已,代码略。

类型转换魔术方法

方法名 含义
__int__(self) 实现整形的强制转换
__long__self) 实现长整形的强制转换,long在py3中和int整合了
__float__(self) 实现浮点型的强制转换
__complex__(self) 实现复数的强制转换
__bin__(self) 实现二进制数的强制转换
__oct__(self) 实现八进制的强制转换
__hex__(self) 实现十六进制的强制转换
__index__(self) 当对象是被应用在切片表达式中时,实现整形强制转换
__trunc__(self) 当使用 math.trunc(self) 的时候被调用,整数截断
__coerce__(self, other) 实现混合模式算数,只在py2有效

3.打印对象的魔术方法

方法名 含义
__str__(self) 定义当 str() 调用的时候的返回值(人类可读)
__repr__(self) 定义当 repr() 被调用的时候的返回值(机器可读)
__unicode__self) 定义当 unicode() 调用的时候的返回值,只在py2中有效
__hash__(self) 定义当 hash() 调用的时候的返回值,它返回一个整形
__nonzero__(self) 定义当 bool() 调用的时候的返回值

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class MyNumber(object):

def __init__(self, num):
self.num = num

def __str__(self):
"""
str(MyNumber(x))
"""
return str(self.num)

def __repr__(self):
"""
repr(MyNumber(x))
"""
return "<{} {}>".format(__class__, str(self.num))

def __unicode__(self):
"""
[only in py2] unicode(MyNumber(x))
"""
return str(self.num)

def __hash__(self):
"""
hash(MyNumber(x))
"""
return hash(self.num)

def __nonzero__(self):
"""
[only in py2] nonzero(MyNumber(x))
"""
return bool(self.num)

def __bool__(self):
"""
[only in py3] bool(MyNumber(x))
"""
return bool(self.num)

num1 = MyNumber(123)
num2 = MyNumber(0)
print(str(num1)) # 123
print(repr(num1)) # <<class '__main__.MyNumber'> 123>
print(hash(num1)) # 123
print(bool(num1)) # True
print(bool(num2)) # False

4.控制对象属性访问的魔术方法

方法名 含义
__getattr__(self, name) 当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为
__setattr__(self, name, value) 定义当试图对一个对象的属性赋值时的行为
__delattr__(self, name) 定义当试图删除一个对象的属性时的行为
__getattribute__(self, name) getattribute 允许你自定义属性被访问时的行为,只能用于新式类,而且很容易引起无限递归调用,可以用过使用父类的getattribute避免,建议不要使用

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person(object):

def __init__(self, name):
self.name = name

def __getattr__(self, name):
print('in __getattr__')
return None

def __setattr__(self, name, value):
print('in __setattr__')
self.__dict__[name] = value

def __delattr__(self, name):
print('in __delattr__')
self.__dict__[name] = None

p = Person("Jack") # in __setattr__
print(p.name) # Jack
print(p.no_exit_attr) # in __getattr__, None
p.name = "Smith" # in __setattr__
print(p.name) # Smith
del p.name # in __delattr__
print(p.name) # None

5.可迭代对象和容器的魔术方法

方法名 含义
__len__(self) 定义调用len()函数时的行为
__getitem__(self, key) 定义获取容器内容时的行为
__setitem__(self, key, value) 定义设置容器内容时的行为
__delitem__(self, key) 定义删除容器内容时的行为
__iter__(self) 定义迭代容器内容时的行为
__contains__(self, item) 定义对容器使用in时的行为
__reversed__(self) 定义对容器使用reversed()时的行为

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

class MyDictIterator:
def __init__(self, n):
self.i = 0
self.n = n

def __iter__(self):
return self

def next(self):
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration()

class MyList(object):

def __init__(self, list=[]):
self.list = list

def __len__(self):
return len(self.list)

def __getitem__(self, key):
print('in __getitem__')
return self.list[key]

def __setitem__(self, key, value):
print('in __setitem__')
self.list[key] = value

def __delitem__(self, key):
print('in __delitem__')
del self.list[key]

def __iter__(self):
print('in __iter__')
return iter(self.list)

def __contains__(self, item):
print('in __contains__')
return item in self.list

def __reversed__(self):
print('in __reversed__')
return reversed(self.list)

list1 = MyList(["foo", "bar", "baz"])
print(len(list1)) # 3
print(list1[0]) # in __getitem__ foo
list1[0] = 'FOO' # in __setitem__
print(list1[0]) # in __getitem__ FOO
del list1[0] # in __delitem__
print(list1[0]) # in __getitem__ bar

for w in list1:
print(w) # in __iter__ bar baz

print("bar" in list1) # in __contains__ True
print("BAR" in list1) # in __contains__ False
print(reversed(list1)) # in __reversed__ <list_reverseiterator object at 0x110005128>

6.反射

方法名 含义
__instancecheck__(self, instance) 检查一个实例是否是你定义的类的一个实例(例如 isinstance(instance, class) )
__subclasscheck__(self, subclass) 检查一个类是否是你定义的类的子类(例如 issubclass(subclass, class) )

7.可调用的对象的魔术方法

方法名 含义
__call__(self, [args…] 使对象可以像函数一样被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point(object):

def __init__(self, x, y):
self.x = x
self.y = y

def __call__(self, x, y):
self.x = x
self.y = y

p = Point(1, 0)
print("({}, {})".format(p.x, p.y)) # (1, 0)
p(2, 1)
print("({}, {})".format(p.x, p.y)) # (2, 1)

定义了__call__方法的类的实例可以像函数一样被调用。

8.会话管理的魔术方法

方法名 含义
__enter__(self) 定义了当会话开始的时候初始化对象的行为
__exit__(self, exception_type, exception_val, trace) 定义了当会话结束时的行为

Python可以通过with来开启一个会话控制器,会话控制器通过两个魔术方法来定义:__enter__(self)__exit__(self, exception_type, exception_val, trace)__enter__定义了当会话开始的时候初始化对象的行为,它的返回值会被with语句的目标或as后面的名字绑定。__exit__定义了当会话结束时的行为,它一般做一些清理工作,比如关键文件等。如果with代码块执行成功,__exit__exception_typeexception_valtrace三个参数都会是None,如果执行失败,你可以在会话管理器内处理这个异常或将异常交由用户处理。如果要在会话管理器内处理异常,__exit__最后要返回True

来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class FileObject(object):

def __init__(self, file):
self.file = file

def __enter__(self):
return self.file

def __exit__(self, exception_type, exception_val, trace):
try:
self.file.close()
except:
print('File close failed!')
return True

with FileObject(open('./test.py')) as file:
print(file) # <_io.TextIOWrapper name='./test.py' mode='r' encoding='UTF-8'>

通过使用会话管理器,我们可以包装对象的打开和关闭操作,减少忘记关闭资源这种误操作。

9.创建对象描述器的魔术方法

方法名 含义
__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)
  1. 拷贝的魔术方法
方法名 含义
__copy__(self)
__deepcopy__(self, memodict=)

11. 对象的序列化和反序列化的魔术方法

方法名 含义
__getinitargs__(self)
__getnewargs__(self)
__getstate__(self)
__setstate__(self, state)