魔术方法 / Magic Method
魔法方法就是可以给你的类增加魔力的特殊方法(实质应称为特殊方法,魔术方法在JavaScript中有所体现,对象具有不透明特性,而且无法在自定义对象中模拟这些行为),如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。它们经常是两个双下划线包围来命名的(比如 __init__,__lt__),Python的魔法方法是非常强大的。下面介绍几种常用的魔术方法
__init__ 方法
__init__ 构造器/构造方法,当一个实例被创建的时候初始化的方法。但是它并不是实例化调用的第一个方法,__new__才是实例化对象调用的第一个方法。
1 class Foo: 2 def __init__(self, param): 3 print('Init method get parameter: %s' % param) 4 f = Foo('PARAM')
上面代码中的 Foo 类定义了一个 __init__ 构造方法,这个方法接收一个参数,但在使用时,这个方法可以不被显式的调用(类继承的时候有时会进行父类构造方法的显式调用),而是通过第 4 行的实例化方式来进行调用,运行上面的代码可以得到结果如下,
Init method get parameter: PARAM
从结果可以看出,在一个类进行实例化生成实例的时候,__init__ 构造方法会被自动调用。
__del__ 方法
__del__ 析构器/析构方法,与 __init__ 相反,当实例被销毁的时候调用的方法,也是 del() 函数会调用的方法。
1 class Foo(): 2 def __init__(self): 3 print('__init__ method called') 4 5 def __del__(self): 6 print('__del__ method called') 7 8 f = Foo() 9 del(f)
上面的代码定义了一个带有构造方法可析构方法的 Foo 类,运行代码可以得到如下结果,
__init__ method called __del__ method called
从结果可以看出,构造和析构两个方法都被调用了,构造方法在第 8 行被调用,而析构方法则是在第 9 行被 del 函数所调用。
__new__ 方法
__new__ 方法是实例化调用的第一个方法,在 __init__ 方法之前调用,它只取下 cls参数,并把其他参数传给 __init__。__new__ 很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。
__new__ 方法使用时注意以下几点:
1. __new__ 是在一个对象实例化的时候所调用的第一个方法;
2. 它的第一个参数是这个类,其他的参数是用来直接传递给 __init__ 方法;
3. __new__ 决定是否要使用自身的 __init__ 方法,因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __new__ 没有返回实例对象,则 __init__ 不会被调用,只有 __new__ 返回了实例对象,__init__ 才会被调用执行;
4. __new__ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string;
5. __new__ return的是一个构建的实例。
__new__ 实现单例模式
1 class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def __new__(cls, *args, **kwargs): 7 print(id(cls), id(Person)) # Id is same 8 # If instance of class has exist, return the instance without generate a new one 9 if not hasattr(cls, 'instance'): 10 cls.instance = super(Person, cls).__new__(cls) # Generate a instance 11 return cls.instance 12 13 a = Person('p1', 20) 14 print(a.name, a.age) # 'p1', 20 15 b = Person('p2', 21) 16 print(a.name, a.age) # 'p2', 21 17 print(b.name, b.age) # 'p2', 21 18 print(a is b) # True
上面的代码利用 __new__ 方法实现了一种单例模式,
首先是定义了一个类以及构造方法,然后定义了这个类的 __new__ 方法,注意这里的 __new__ 方法接收的第一个参数为cls,也就是当前的类,可以通过第 7 行输出两者的 id 进行比较,最终可以发现两者 id 相同。
在第 8-10 行中,利用一个判断语句判断当前类中是否含有 instance 属性,若有则直接返回,若没有则利用 super 通过 MRO 查找到 Person 的 MRO 搜索顺序中的下一个类(本例中是 object)来调用其 __new__ 方法,并传入当前 cls 作为参数。这样便实现了一个简单的单例模式。
然后是第 11 行,返回一个实例,这个返回的实例将会作为 __init__ 方法的第一个参数传给 self。
最后对单例模式进行验证,在 12-13 行中生成一个实例 a,并查看内部属性,发现其结果与传入值相同,在 15-17 行再生成一个实例 b,同时对 a 和 b 进行查看发现, a 的属性由于 b 的实例化也被改变了,最后通过 18 行查看发现,a 和 b 是同一个实例。
也就是说,无论 Person 实例化多少次,都只会产生一个实例对象传给 __init__ 方法,只不过每次的初始化函数参数不同,从而改变了实例的属性值。
相关阅读
1. super
2. MRO 搜索顺序