魔术方法(Magic Method)

魔术方法(Magic Method)

在 python 中,所有以__包裹的方法统称为 魔术方法,如:__init__,__str__等等

1. 构造(__new__)和初始化(__init__)

  • __new__: 用来创建类并返回这个类的实例
    • 创建实例过程中必定会被调用
  • __init__: 只是将传入的参数来初始化该实例
    • 创建实例时不一定会被调用, 比如通过 pickle.load的方式反序列化实例时, __init__不会被调用

  • def __new__(cls) 是在 def __init__(self) 之前调用的,作用是返回一个实例对象.
  • __new__方法总是需要返回该类的一个实例,而__init__不能返回除了None的任何值.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User:
def __new__(cls, *args, **kwargs):
# 打印 __new__方法中的相关信息
print('调用了 def __new__ 方法')
print(args)
# 最后返回父类的方法
return super(User, cls).__new__(cls)

def __init__(self, name, age):
print('调用了 def __init__ 方法')
self.name = name
self.age = age

if __name__ == '__main__':
user = User('小明', 18)

# 执行结果
调用了 def __new__ 方法
('小明', 18)
调用了 def __init__ 方法

通过打印的结果来看,我们就可以知道一个类创建的过程是怎样的了,先是调用了 __new__ 方法来创建一个对象,把参数传给 __init__ 方法进行实例化。

在实际开发中,很少会用到 __new__ 方法,除非希望能够控制类的创建。通常讲到 __new__ ,都是牵扯到 metaclass(元类)的。

2. 属性的访问控制

Python 定义私有属性,然后提供公共的setget方法时,可以通过魔术方法来实现封装.

方法说明
__getattr__(self, name)该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。
__setattr__(self, name, value)定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意,实现 __setattr__ 时要避免”无限递归”的错误,
__delattr__(self, name)__delattr____setattr__ 很像,只是它定义的是你删除属性时的行为。实现 __delattr__ 是同时要避免”无限递归”的错误
__getattribute__(self, name)__getattribute__ 定义了你的属性被访问时的行为,相比较,__getattr__ 只有该属性不存在时才会起作用。因此,在支持 __getattribute__的 Python 版本,调用__getattr__ 前必定会调用 __getattribute__``__getattribute__ 同样要避免”无限递归”的错误。

通过上面的方法表可以知道,在进行属性访问控制定义的时候你可能会很容易的引起一个错误,可以看看下面的示例:

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
# 定制特有属性

上面方法的调用案例:

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
class User:
def __getattr__(self, name):
print('调用了 __getattr__ 方法')
return super(User, self).__getattr__(name)

def __setattr__(self, name, value):
print('调用了 __setattr__ 方法')
return super(User, self).__setattr__(name, value)

def __delattr__(self, name):
print('调用了 __delattr__ 方法')
return super(User, self).__delattr__(name)

def __getattribute__(self, name):
print('调用了 __getattribute__ 方法')
return super(User, self).__getattribute__(name)


if __name__ == '__main__':
user = User()
user.attr1 = True # 设置属性值, 会调用 __setattr__ 方法
user.attr1 # 属性存在, 只有 __getattribute__ 方法会调用
try:
user.attr2 # 属性不存在, 先调用 __getattribute__ 方法, 再调用 __getattr__ 方法
except AttributeError:
pass
del user.attr1 # 删除属性, 会调用 __delattr__ 方法


# ------------ 运行结果 -----------------
调用了 __setattr__ 方法
调用了 __getattribute__ 方法
调用了 __getattribute__ 方法
调用了 __getattr__ 方法
调用了 __delattr__ 方法

3. 对象的描述器

描述器: 有 __get____set____delete__ 方法的对象
默认对属性的访问控制是从对象的字典里面(__dict__)中获取(get),设置(set)和删除(delete)

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
class User:
def __init__(self, name='Pupper', sex='男'):
self.name = name
self.sex = sex

def __get__(self, obj, objtype):
print('获取 name 值')
return self.name

def __set__(self, obj, value):
print('设置 name 值')
self.name = value

class MyClass:
x = User()
y = 5


if __name__ == '__main__':
m = MyClass()
print(m.x) # 调用的是 User.__get__ 方法
print("------------------------------")
m.x = 'Pupper Cheng' # 调用的是 User.__set__ 方法
print(m.x) # 调用的是 User.__get__ 方法
print("------------------------------")
print(m.y) # 直接获取 MyClass.y 的值


# -------------- 输出结果 -------------------
获取 name 值
Pupper
------------------------------
设置 name 值
获取 name 值
Pupper Cheng
------------------------------
5

距离既可以用单位”米”表示,也可以用单位”英尺”表示。 现在我们定义一个类来表示距离,它有两个属性: 米和英尺

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
class Meter:
def __init__(self, value=0.0):
self.value = float(value)

def __get__(self, instance, owner):
print('调用 meter 的 __get__ 方法')
return self.value

def __set__(self, instance, value):
print('调用 meter 的 __set__ 方法')
self.value = float(value)


class Foot:
def __get__(self, instance, owner):
print('调用 foot 的 __get__ 方法')
return instance.meter * 3.2808

def __set__(self, instance, value):
print('调用 foot 的 __set__ 方法')
instance.meter = float(value) / 3.2808


class Distance:
meter = Meter()
foot = Foot()


if __name__ == '__main__':
d = Distance()
print(d.meter, d.foot)
print('----------------------------------------')
d.meter = 1
print(d.meter, d.foot)
print('----------------------------------------')
d.meter = 2
print(d.meter, d.foot)


# -------------- 输出结果 ----------------------
调用 meter 的 __get__ 方法
调用 foot 的 __get__ 方法
调用 meter 的 __get__ 方法
0.0 0.0
----------------------------------------
调用 meter 的 __set__ 方法
调用 meter 的 __get__ 方法
调用 foot 的 __get__ 方法
调用 meter 的 __get__ 方法
1.0 3.2808
----------------------------------------
调用 meter 的 __set__ 方法
调用 meter 的 __get__ 方法
调用 foot 的 __get__ 方法
调用 meter 的 __get__ 方法
2.0 6.5616

在上面例子中,在还没有对 Distance 的实例赋值前, 我们认为 meter 和 foot 应该是各自类的实例对象, 但是输出却是数值。这是因为 get 发挥了作用.

我们只是修改了 meter ,并且将其赋值成为 int ,但 foot 也修改了。这是 set 发挥了作用.

描述器对象 (Meter、Foot) 不能独立存在, 它需要被另一个所有者类 (Distance) 所持有。描述器对象可以访问到其拥有者实例的属性,比如例子中 Foot 的 instance.meter 。

4. 自定义容器(Container)

  • 常见的容器类型: dicttupleliststring
  • 不可变容器: tuplestring
  • 可变容器: dictlist
功能说明
自定义不可变容器类型需要定义 __len____getitem__ 方法
自定义可变类型容器在不可变容器类型的基础上增加定义 __setitem____delitem__
自定义的数据类型需要迭代需定义 __iter__
返回自定义容器的长度需实现 __len__(self)
自定义容器可以调用 self[key] ,如果 key 类型错误,抛出 TypeError ,如果没法返回 key 对应的数值时,该方法应该抛出 ValueError需要实现 __getitem__(self, key)
当执行 self[key] = value调用是 __setitem__(self, key, value)这个方法
当执行 del self[key] 方法其实调用的方法是 __delitem__(self, key)
当你想你的容器可以执行 for x in container: 或者使用 iter(container)需要实现 __iter__(self) ,该方法返回的是一个迭代器

使用上面魔术方法实现 Haskell 语言中的一个数据结构:

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
class FunctionalList:
"""实现了内置类型 list 的功能,并丰富了一些其他方法: head, tail, init, last, drop, take"""
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values

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

def __getitem__(self, key):
return self.values[key]

def __setitem__(self, key, value):
self.values[key] = value

def __delitem__(self, key):
del self.values[key]

def __iter__(self):
return iter(self.values)

def __reversed__(self):
return FunctionalList(reversed(self.values))

def append(self, value):
self.values.append(value)

def head(self):
# 获取第一个元素
return self.values[0]

def tail(self):
# 获取除第一个元素外的所有元素
return self.values[1:]

def init(self):
# 获取除最后一个元素外的所有元素
return self.values[:-1]

def last(self):
# 获取最后一个元素
return self.values[-1]

def drop(self, n):
# 获取除前 n 个元素外的所有元素
return self.values[n:]

def take(self, n):
# 获取前 n 个元素
return self.values[:n]

5. 运算符相关的魔术方法

5.1 比较运算符

魔术方法说明
__cmp__(self, other)如果该方法返回负数,说明 self < other; 返回正数,说明 self > other; 返回 0 说明 self == other。强烈不推荐来定义 __cmp__ , 取而代之, 最好分别定义 __lt__, __eq__ 等方法从而实现比较功能。 __cmp__ 在 Python3 中被废弃了。
__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
45
46
47
48
49
50
51
52
53
class Number(object):
def __init__(self, value):
self.value = value

def __eq__(self, other):
print('__eq__')
return self.value == other.value

def __ne__(self, other):
print('__ne__')
return self.value != other.value

def __lt__(self, other):
print('__lt__')
return self.value < other.value

def __gt__(self, other):
print('__gt__')
return self.value > other.value

def __le__(self, other):
print('__le__')
return self.value <= other.value

def __ge__(self, other):
print('__ge__')
return self.value >= other.value


if __name__ == '__main__':
num1 = Number(2)
num2 = Number(3)
print('num1 == num2 ? --------> {} \n'.format(num1 == num2))
print('num1 != num2 ? --------> {} \n'.format(num1 == num2))
print('num1 < num2 ? --------> {} \n'.format(num1 < num2))
print('num1 > num2 ? --------> {} \n'.format(num1 > num2))
print('num1 <= num2 ? --------> {} \n'.format(num1 <= num2))
print('num1 >= num2 ? --------> {} \n'.format(num1 >= num2))


# ---------------- 运行结果 ------------------
__eq__
num1 == num2 ? --------> False
__eq__
num1 != num2 ? --------> False
__lt__
num1 < num2 ? --------> True
__gt__
num1 > num2 ? --------> False
__le__
num1 <= num2 ? --------> True
__ge__
num1 >= num2 ? --------> False

5.2 算数运算符

魔术方法说明
__add__(self, other)实现了加号运算
__sub__(self, other)实现了减号运算
__mul__(self, other)实现了乘法运算
__floordiv__(self, other)实现了 // 运算符
___div__(self, other)实现了/运算符. 该方法在 Python3 中废弃. 原因是 Python3 中,division 默认就是 true division
__truediv__(self, other)实现了 true division. 只有你声明了 from __future__ import division 该方法才会生效
__mod__(self, other)实现了 % 运算符, 取余运算
__divmod__(self, other)实现了 divmod() 內建函数
__pow__(self, other)实现了 ** 操作. N 次方操作
__lshift__(self, other)实现了位操作 <<
__rshift__(self, other)实现了位操作 >>
__and__(self, other)实现了位操作 &
__or__(self, other)实现了位操作 ``
__xor__(self, other)实现了位操作 ^