Python魔法方法进阶
纸上得来终觉浅,绝知此事要躬行。

在Python中,所有以双下划线包起来的方法,都统称为“魔术方法”。比如我们接触最多的init__。有些魔术方法,我们可能一辈子都不会再遇到了。而有些魔术方法,巧妙使用它可以构造出非常优美的代码,比如将复杂的逻辑封装成简单的API。介绍的顺序大概是:常见的先介绍,越少见的越靠后讲。
魔术方法 | 调用方式 | 解释 |
---|---|---|
__new__(cls [,...]) | instance=MyClass(args) | 在创建实例的时候被调用 |
__init__(self [,...]) | instance=MyClass(args) | 在创建实例的时候被调用 |
__cmp__(self, other) | self==other, self>other | 在比较的时候调用 |
__pos__(self) | +self | 一元加运算符 |
__neg__(self) | -self | 一元减运算符 |
__invert__(self) | ~self | 取反运算符 |
__index__(self) | x[self] | 对象被作为索引使用的时候 |
__nonzero__(self) | bool(self) | 对象的布尔值 |
__getattr__(self, name) | self.name | 访问一个不存在的属性时 |
__setattr__(self, name, val) | self.name=val | 对一个属性赋值时 |
__delattr__(self, name) | del self.name | 删除一个属性时 |
__getattribute__(self, name) | self.name | 访问任何属性时 |
__getitem__(self, key) | self[key] | 使用索引访问元素时 |
__setitem__(self, key, val) | self[key] = val | 对某个索引值赋值时 |
__delitem__(self, key) | del self[key] | 删除某个索引值时 |
__iter__(self) | for x in self | 迭代时 |
__contains__(self, value) | value [not] in self | 使用 in 操作测试关系时 |
__concat__(self, value) | self+other | 连接两个对象时 |
__call__(self [,...]) | self(args) | “调用”对象时 |
__enter__(self) | with self as x: | with 语句环境管理 |
__exit__(self, exc, val, trace) | with self as x: | with 语句环境管理 |
__getstate__(self) | pickle.dump(file, self) | 序列化 |
__setstate__(self) | data=pickle.load(file) | 序列化 |
1. 构造函数
控制属性访问特点
魔法函数 | 解释说明 |
---|---|
__new__ | 构造函数 |
__init__ | 初始化函数 |
__del__ | 删除函数 |
函数作用
- 用于创建对象时,初始化对象,也就是为对象成员变量赋初始值。
注意要点
- __new__要早于__init__的执行
- __new__是类方法而__init__是实例方法
示例说明
- 构造方法并不负责创建实例,而是用来初始化实例变量
# __new__用户设置初始化类变量PAYLOAD的值 1. 这里的super函数让其余变量继承自父类,再对新变量赋值 class ExampleClass: def __new__(cls, *args, **kwargs): print('Create new instance...') instance = super().__new__(cls) instance.PAYLOAD = (args, kwargs) return instance def __init__(self, payload): print('Init instance...') self.payload = payload def __del__(self): print('Del instance...')
In [1]: from func import ExampleClass In [2]: ec = ExampleClass([1, 2]) Create new instance... Init instance... In [3]: ec.PAYLOAD Out[3]: (([1, 2],), {}) In [4]: ec.payload Out[4]: [1, 2] In [5]: del ec Del instance...
- 如果__new__函数不创建实例,__init__函数是不会执行的
# 这里并没有使用super函数,而是直接返回了object的__new__方法 In [1]: class ExampleClass: ...: def __new__(cls, *args, **kwargs): ...: print("call __new__") ...: print(type(cls)) ...: return object.__new__(cls) ...: ...: def __init__(self, x): ...: print("call __init__") ...: print(type(self)) ...: s1 = set(dir(self)) ...: self.x = x ...: s2 = set(dir(self)) ...: print(s2-s1) ...: In [2]: a = ExampleClass(5) call __new__call __init__ {'x'}
In [3]: class ExampleClass: ...: def __new__(cls, *args, **kwargs): ...: print("call __new__") ...: print(type(cls)) ...: ...: def __init__(self, x): ...: print("call __init__") ...: print(type(self)) ...: s1 = set(dir(self)) ...: self.x = x ...: s2 = set(dir(self)) ...: print(s2-s1) ...: In [4]: a = ExampleClass(5) call __new__
2. 对象可视化
- 控制属性访问特点
魔法函数 | 解释说明 |
---|---|
__repr__ | 字符串(程序友好) |
__str__ | 字符串(用户友好) |
__bytes__ | 二进制 |
- 代码示例说明
In [1]: class A: ....: def __init__(self, name): ....: self.name = name ....: def __repr__(self): ....: return self.name ....: def __str__(self): ....: return 'call __str__ name is {0}'.format(self.name) ....: def __bytes__(self): ....: return 'call _bytes__ name is {0}'.format(self.name).encode('utf-8') ....: In [2]: cls = A('escape') 1. __repr__() In [3]: cls Out[3]:1. __str__() In [4]: print(a) call __str__ name is escape 1. __repr__() In [5]: repr(a) Out[5]: escape 1. __str__() In [5]: str(a) Out[5]: 'call __str__ name is escape' 1. __bytes__() In [6]: bytes(a) Out[6]: b'call _bytes__ name is escape'
3. 反射
可以使用代码获取对象本身的一些属性,如对象的字段、方法等,就算是反射。
- 魔术方法特点
魔法函数 | 解释说明 |
---|---|
xxx.__class__ | 获取当前实例的类名 |
xxx.__doc__ | 获取文档字符串 |
xxx.__dir__ | 内建函数 dir 实际调用的函数 |
xxx.__dict__ | 获取此实例持有的所有变量 |
xxx.__module__ | 返回当前所在的模块 |
xxx.__name__ | 获取类的名称,实例没有 |
- 代码示例说明
In [34]: class ExcapleClass: ....: X = 1 ....: Y = 2 ....: Z = 3 ....: def __init__(self, x, y, z): ....: self.x = x ....: self.y = y ....: self.z = z ....: def method(self): ....: pass ....: In [35]: ec = ExcapleClass(1,2,3) In [36]: ec.__doc__ In [37]: ec.__dict__ Out[37]: {'x': 1, 'y': 2, 'z': 3} In [38]: ec.__class__ Out[38]: __main__.Grok In [39]: ec.__dir__ Out[39]:In [40]: ec.__dir__() ......
4. 比较运算符重载
- 控制属性访问特点
魔法函数 | 解释说明 |
---|---|
__lt__ | 小于 |
__le__ | 小于等于 |
__eq__ | 等于 |
__nq__ | 不等于 |
__gt__ | 大于 |
__ge__ | 大于等于 |
- 代码示例说明
# 需要在对象定义的时候通过比较运算重载来实现比较的 In [1]: class Person: ....: def __init__(self, age): ....: self.age = age ....: def __lt__(self, other): ....: return self.age < other.age ....: def __le__(self, other): ....: return self.age <= other.age ....: def __eq__(self, other): ....: return self.age == other.age ....: def __nq__(self, other): ....: return self.age == other.age ....: def __gt__(self, other): ....: return self.age > other.age ....: def __ge__(self, other): ....: return self.age >= other.age ....: In [2]: p1 = Person(18) In [3]: p2 = Person(14) In [4]: p1 > p2 Out[4]: True In [5]: p1 < p2 Out[5]: False
5. 上下文管理器
with后面紧跟一个实例,这个实例具有enter和exit方法。

魔术方法特点
魔法函数 | 解释说明 |
---|---|
__enter__ | 进入with块后立即执行__enter__方法 |
__exit__ | 退出with块后立即执行__exit__方法 |
方法特性说明
- as子句用于把__enter__方法的返回值,赋值给一个变量
- __exit__方法在报错的时候,返回三个参数的一个元组(exc_type, exc, traceback)
- exc_type表示报错的类型
- exc表示报错的信息
- traceback表示堆栈信息
方法使用场景
- 成对出现操作,并且必须确保两个都执行
- 资源打开和关闭
- 加锁和解锁
- 进入代码块之前需要执行一些操作
- 初始化
- 权限判断
- 离开代码块之后需要执行一些操作
- 关闭资源连接
- 解锁
代码示例说明
- 这里我们引入的是一个简单的示例进行方式和演示
# 当socket出现问题,连接也是会正常关闭的,不用手动关闭连接 import socket class SocketClient: def __init__(self, host, port): self.host = host self.port = port self.conn = socket.socket() def __enter__(self): self.conn.connect((self.host, self.port)) return self.conn 1. 接收三个参数 __exit__(self, exc_type, exc, traceback) def __exit__(self, *args, **kwargs): self.conn.close() with SocketClient('192.168.1.199', 8000) as conn: print(conn)
- 相关的库contextlib可以帮助我们更好的使用上下文管理
import contextlib @contextlib.contextmanager def connect(host, port): conn = socket.socket() try: conn.connect((host, port)) yield conn finally: conn.close() with connect('192.168.1.199', 8000) as conn: print(conn)
6. 属性访问控制
使用字符串操作对象的属性,通过点操作符操作的都可以使用,如属性、方法等。
控制属性访问特点
魔法函数 | 解释说明 |
---|---|
__getattr__ | 在属性被访问而对象没有这样的属性时自动调用 |
__setattr__ | 试图给属性赋值时自动调用 |
__delattr__ | 试图删除属性时自动调用 |
__getattribute__ | 在属性被访问时自动调用,且只适用于新式类;它和__getattr__的区别是无论属性是否存在,都要被调用;通过访问该方法无法获取时,再使用__getattr__获取,依旧无法获取则报错 |
特殊方法说明
特殊方法 | 解释说明 |
---|---|
getattr | 调用__getattribute__方法,通过字符串的方式访问类的属性,等价于a.num |
setattr | 实际调用__setattr__方法,通过属性名修改属性,等价于a.num=3 |
delattr | 实际调用__delattr__方法,通过属性名删除属性,等价于del a.num |
# getattr getattr(...) getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case.
# setattr setattr(...) setattr(object, name, value) Set a named attribute on an object; setattr(x, 'y', v) is equivalent to ``x.y = v''.
# delattr delattr(...) delattr(object, name) Delete a named attribute on an object; delattr(x, 'y') is equivalent to ``del x.y''.
代码示例说明
- 对于getattr、setattr、delattr三个属性的初步了解。
class Attr: X = 1 Y = 2 def __init__(self, name): self.name = name def show_num(self): return self.name def __setattr__(self, name, value): print('settattr') def __getattr__(self, name): print('getattr') def __getattribute__(self, name): print('getattribute') def __delattr__(self, name): print('delattr') 1. 我们由此可以看出,外部调用都是我们自己定义的方法 In [1]: from magic import Attr In [2]: a = Attr('escape') getattribute getattribute settattr In [3]: getattr(a, 'X') getattribute In [4]: setattr(a, 'name', 'attr') settattr In [5]: delattr(a, 'name') delattr
- getattr方法有第三个可选参数,用于方法不存在时的替补方案
class WorkerInterface: def method1(self): print(1) def method2(self): print(2) def method3(self): print(3) class WorkerImpl: def method2(self): print('impl 2') imterface = WorkerInterface() impl = WorkerImpl() getattr(impl, 'method1', imterface.method2)() ==> 返回,2 getattr(impl, 'method2', imterface.method2)() ==> 返回,impl2
- 在调用__getattribute__的时候,最后返回的时候用的是object.__getattribute__而没有用self.__getattribute__。这是由于,如果使用了self那么无论使用了self中的什么属性,最后都会触发__getattribute__的调用,这样就陷入了一个死循环中了。
# 其中getattr方法调用的我们自定义的__getattribute__方法 1. 而其他的方法,则都使用object的方法,为了避免陷入死循环中 1. ...写法是Python3.6中的新用法,类似于pass这样的用法,可以等价使用 class User: ... class Proxy: title = '代理' _data = User() def show_title(self): return self.title def __getattr__(self, name): print('use __getattr__') return getattr(self._data, name) def __setattr__(self, name, value): print('use __setattr__') return object.__setattr__(self._data, name, value) def __delattr__(self, name): print('use __delattr__') return object.__delattr__(self._data, name) def __getattribute__(self, name): if name in ('_data', 'title', 'show_title'): return object.__getattribute__(self, name) print('use __getattribute__: {}'.format(name)) if name.startswith('b'): raise AttributeError return object.__getattribute__(self._data, name)
In [1]: from func import Proxy In [2]: p = Proxy() In [3]: p.title Out[3]: '代理' In [4]: p.show_title() Out[4]: '代理' In [5]: p.a = 1 use __setattr__ In [6]: p.a use __getattribute__: a Out[6]: 1 In [7]: p.b = 2 use __setattr__ In [8]: p.b use __getattribute__: b use __getattr__ Out[8]: 2 In [9]: p._data.b Out[9]: 2 In [10]: del p.b use __delattr__ In [11]: p.b use __getattribute__: b use __getattr__ --------------------------------------------------------------------------- AttributeError Traceback (most recent call last)in () ----> 1 p.b ~/Escape/MorePractise/func.py in __getattr__(self, name) 12 def __getattr__(self, name): 13 print('use __getattr__') ---> 14 return getattr(self._data, name) 15 16 def __setattr__(self, name, value): AttributeError: 'User' object has no attribute 'b'
7. 容器方法
容器方法特点
魔法函数 | 解释说明 |
---|---|
__getitem__ | 得到给定键(key)的值 |
__setitem__ | 设置给定键(key)的值 |
__delitem__ | 删除给定键(key)的值 |
__slots__ | 阻止在实例化类时为实例分配 dict 属性 |
__len__ | 获得项的数目 |
slots方法的用处
- 方法优点
- <1> 减少内存使用、
- <2> 限制对实例添加新的属性
- 方法缺点
- <1> 不可以被继承、
- <2> 不可以动态添加新的属性
- 方法用途
- __slots__的作用是为了阻止在实例化类时为实例分配dict属性。默认情况下每个类都会有一个dict,这个dict维护了这个实例的所有属性,可以通过__dict__来访问。