python魔法方法详解
1. 什么是魔法方法
魔法方式(Magic methods)是python的内置函数,一般以双下划线开头和结尾,比如__add__,__new__等。每个魔法方法都有对应的一个内置函数或者运算符。当我们个对象使用这些方法时,相当于对这个对象的这类方法进行重写(如运算符重载)。魔法方法的存在是对类或函数进行了提炼,供python解释器直接调用。当使用len(obj)
时,实际上调用的就是obj.__len__
方法,其它同理。
如我们对某个类A定义了__add__方法,那么可以直接通过a1+a2(a1,a2分别为A的实例)来实现自定义的“相加”,python会识别+并调用该对象对应的__add__方法来运行,而无需像一般的方法一样通过a1.add(a2)来实现。这就是为什么说魔法方法本质是对一些函数进行了提炼和封装,可以方便的赋予对象更多的方法。
dir()可以查看对象的所有方法和属性,其中双下划线开头和结尾的就是该对象具有的魔法方法。以整数对象为例:
>>>dir(int)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
可以看到,整数对象下具有__add__
方法,这也是为什么我们可以直接在python中运算1+2
,当python识别到+
时,就去调用该对象的__add__
方法来完成计算。比如我们想给自己的对象定义+方法:
class A:
def __init__(self,a):
self.a=a
def __add__(self,others):
print('this is a magic method')
return [self.a,others.a]
>>>a=A(1)
>>>b=A(3)
>>>c=a+b#自定义的__add__方法,实现是将两个对象的a属性合并到一个列表中
this is a magic method#+调用的是__add__方法
>>>c
[1, 3]
>>>a.__add__(b)#等价于直接调用
this is a magic method
[1, 3]
同理,再举一个__len__
魔法方法的例子来帮助理解。我们定义一个list对象l,通过dir(l)可以看到该对象中具有__len__
方法,即表明在python中可以通过len(l)来返回其列表长度。我们在自己定义的对象中当然也可以自定第该方法,来实现我们想通过len()返回的结果。
>>>l=[1,2,3]
>>>dir(l)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>>len(l)
3
自定义一个对象:
class B:
def __init__(self,a):
self.a=a
>>>b=B(1)
>>>len(b)#因为对象中未定义__len__,因此调用len(b)会报错,没有该方法
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: object of type 'B' has no len()
#添加__len__方法
class B:
def __init__(self,a):
self.a=a
def __len__(self):
print('this is magic method len')
return 2
>>>a=B(1)
>>>print(len(a))
this is magic method len
2
可以看到,魔术方法在类或对象的某些事件出发后会自动执行,如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。使用这些「魔法方法」,我们可以非常方便地给类添加特殊的功能。
2. 几类常用的魔法方法
魔法方法大致分为如下几类:
- 构造与初始化
- 类的表示
- 访问控制
- 比较、运算等操作
- 容器类操作
- 可调用对象
- 序列化
2.1 构造与初始化
对类的初始化一般会涉及三个魔法方法,__init__
,__new__
和__del__
;
当我们初始化一个类是,a=A(1),首先调用的并不是__init__
函数,而是__new__
;初始化一个类有两步,分别为:
a. 调用该类的__new__
方法,并返回该类的实例对象;
b. 调用该类的__init__
方法,对实例对象进行初始化。
其中__new__
用法如下:
(1) __new__(cls,*args,**kwargs)
:至少要有一个参数cls
,代表传入的类,此参数在实例化时由 Python 解释器自动提供,若返回该类的对象实例,后面的参数直接传递给__init__
。
(2) __new__
可以决定是否使用__init__
方法,但是,执行了__new__
,并不一定会进入__init__
,只有__new__
返回了,当前类cls
的实例,当前类的__init__
才会进入。即使返回父类的实例也不行,必须是当前类的实例;
(3) object将__new__()
方法定义为静态方法,并且至少需要传递一个参数cls,cls表示需要实例化的类,此参数在实例化时由Python解释器自动提供。
(4) __init__()
有一个参数self,该self参数就是__new__()
返回的实例
举例理解:
class A:
def __init__(self,a,b):
print('this is A init')
print(self)
self.a=a
self.b=b
def __new__(cls, *args, **kwargs):
print('this is A new')
print('args:%s'%args)
print('kwargs:%s'%kwargs)
print(cls)
print(object.__new__(cls<