Python中的魔术方法是非常有意思的一部分,通过魔术方法可以做到去自定义化一个类的默认行为,比如相加或者相减,可以把一个对象变为可以迭代的对象,可以控制属性的设置和获取,可以使用with来进行上下文控制。
在Python中的魔术方法是类中的以双下划线开头和结尾的方法,通过较少的代码量,就可以实现非常强大的功能。这里根据Python的中魔术方法被调用的时机,将魔术方法简单分为三种,在类的实例化和销毁的过程中的魔术方法、对象之间使用运算符运算时参与的魔术方法和获取对象属性和数据时调用的魔术方法。
实例化过程中和对象销毁时
在一类魔术方法主要有以下几个
class A: @classmethod def __new__(cls, *args, **kwargs): ... def __init__(self): ... def __enter__(self): ... def __exit__(self, exc_type, exc_val, exc_tb): ... def __del__(self): ... def __call__(self): ...
在上面的几个魔术方法中,除了__new__
方法是一个类方法,其余的都为实例方法。既然将他们归为生命周期中,是因为这些方法会在类(对象实例)执行过程中的不同时机进行运行。对于一个常规的类,在他从被实例化到被销毁的全过程中,他们的调用顺序是这样的__new__
> __init__
> __del__
。而对于其余的两个魔术方法只有在进入上下文管理(使用with
语句)时才会被调用,具体的调用时机如下所示。
with A() as a: print('在调用A()时回调用__enter__方法') pass print('在这里运行__exit__方法')
__enter__
和__exit__
方法常用作一些文件管理中,比如__enter__
中将文件打开,__exit__
中将文件关闭,这样在使用的时候,就可以使用with
来实现自动打开文件和关闭文件了。
class File: def __init__(self,path): self.path = path def __enter__(self): self.fp = open(self.path) def __exit__(self, exc_type, exc_val, exc_tb): self.fp.close() with File('1.txt') as fp: # do something
除了实现自动打开和自动关闭文件之外,这种方式还可以用来自动管理各种连接,比如数据连接,在__enter__
中打开数据库连接__exit__
中关闭数据库连接。
__del__
方法不止在程序退出对象销毁时被调用,当主动通过del
去删除一个对象的时候,也会被调用。这个函数通常是做一些收尾工作,比如将用不到变量del
掉,等等。__init__
方法又叫实例化方法,是因为在类实例化的时候这个方法会被调用。通过self.
的方式可以将一些变量绑定到对象上,这些绑定的对象将存储在对象的__dict__
字典中。__new__
方法一般情况下不会用到,只有当你需要控制类的实例化过程的时候才会用得到,比如一些特殊用途的类,比如用来做ORM
(数据库连接映射)的时候才会需要自定义类的实例化过程,再比如实现一些单例模式的时候也会用得到这个方法。__call__
方法严格意义上可能不算生命周期类的魔术函数,他的作用是可以将一个实例化的对象直接当成一个函数来进行调用。
a = A() a() # 这里就是因为__call__的存在,所以可以直接调用
运算时
在Python中一切皆为对象,在Python中常见的数字比如1
和2
,他们是属于int
类型的对象,当他们之间进行运算时,比如加法运算。其实这背后是调用了int
这个对象的__add__
魔术方法。通过改变__add__
方法就可以控制加法运算,除了加法运算,Python中的其他「所有」运算都是可以通改变运算符对应的魔术方法来改变其默认行为的。换句话说,Python的所有运算符都是可以自定义的,这也被称为运算符重载。由于和运算符相关的魔术方法太多,这里只讲两个的使用这类魔术方法实现的方法。
__truesection__
对应的运算符是/
符号。这本身是一个除法符号,但这个符号和路径里面的分隔符是一样的/test/a
,通过对其对应的魔术方法重载,就可以做到使用/
符号来直接进行路径的拼接。作为演示,这里实现了一个极其简单的版本,更加完整的实现方式可以参考Python标准库里面的pathlib
的实现。
import os class Path: def __init__(self,path): self.path = path def __truesection__(self,other): return Path(os.path.join(str(self.path),str(other))) def __str__(self): return self.path p = Path('test') print(p / 'a') # /test/a
__contains__
对应的运算符是in
成员运算符,在判断一个元素是否在一个对象中时,就可以重载该运算符的魔术方法。
class Numbers: numbers = [] def add(self, x): self.numbers.append(x) def pop(self): self.numbers.pop() def __contains__(self,x): return x in self.numbers box = Numbers() box.add(1) box.add(2) box.add(3) print(1 in box) # True
数据获取时
未完待续...