本篇博客主要介绍Python的魔法函数。在进行深度学习的工作或者python的编程时,或多或少会涉及到Python的类编写,其中会涉及到python的魔法函数,如编写一个数据加载的生成器的时候,可能会涉及到__next__
,__iter__
函数,当然生成器可能一个关键字yield
就可以搞定了。最后为了加深对Python魔法函数的理解,这篇博客以代码加说明的方式,记录一些常见的Python魔法函数。
魔法方法是Python的内置函数,一般以双下划线开头,每个魔法方法对应的一个内置函数或者运算符,比如当使用len(obj)
的时候实际上是调用obj.__len__
方法。因此当我们对象使用这些方法的时候,相当于对这个对象的这类方法进行重写或重载。
通过dir()
可以查看对象的所有方法和属性,其中双下划綫开头和结尾的就是该对象具有的魔法方法。以整数对象为例:
常用的魔法方法大致可以分为以下几类:
对类的初始化一般涉及到三个魔法方法:__init__
,__new__
,__del__
。
初始化一个类的时候,如class_a = class_A(1)
,首先调用的是该类的__new__
方法,返回该类的实例对象,然后该类的__init__
方法,对该对象进行初始化。
__new__
方法使用如下:
__new__(cls,*args,**kwargs)
:至少要有一个参数cls,代表传入的类,此参数在实例化时由 Python 解释器自动提供,若返回该类的对象实例,后面的参数直接传递给__init__
。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))#<__main__.A object at 0x000001BCD98FB3D0>,一个A的对象实例
return object.__new__(cls)#创建实例并返回
>>>a=A(1,b=10)
this is A new#先进入__new__
args:1
kwargs:{'b': 10}
<__main__.A object at 0x000001BCD98FB3D0>
this is A init#再进入__init__
<__main__.A object at 0x000001D0BC3EB3D0>#self就是__new__返回的对象实例
__new__
可以决定是否使用__init__
方法,但是,执行了__new__
,并不一定会进入__init__
,只有__new__
返回了,当前类cls的实例,当前类的__init__
才会进入。即使返回父类的实例也不行,必须是当前类的实例;class A:
def __init__(self,a,b):
print('this is A init')
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)
>>>m=A(1,b=10)
this is A new
args:1
kwargs:{'b': 10}
>>>print(m.a)#报错,未进入到当前类的__init__进行初始化
AttributeError: 'NoneType' object has no attribute 'a'
__new__()
方法定义为静态方法,并且至少需要传递一个参数cls,cls表示需要实例化的类,此参数在实例化时由Python解释器自动提供。__init__()
有一个参数self,该self参数就是__new__()
返回的实例__new__
的使用场景如单例模式、工厂模式,以及一些不可变对象的继承上。这类应用非常值得关注并使用,可以大大的让代码看起来优美和简洁;
__del__
方法则是当对象被系统回收的时候调用的魔法方法,在对象生命周期调用结束时调用该方法。Python 采用自动引用计数(ARC)方式来回收对象所占用的空间,当程序中有一个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 1;当程序中有两个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 2,依此类推,如果一个对象的引用计数变成了 0,则说明程序中不再有变量引用该对象,表明程序不再需要该对象,因此 Python 就会回收该对象。所以大部分时候,都不需要我们手动去删掉不再使用的对象,python的回收机制会自动帮我们做这件事。
类的表示相关的魔法方法主要有__str__
、__repr__
和__bool__
__str__
主要是在打印对象print(obj)时,会隐式调用str(obj),即调用类中的__str__
方法;定了该方法就可以通过str(obj)来调用;__repr__
主要式在直接输出对象时的显示,会调用__repr__
方法;定义了该方法就可以通过repr(obj)来调用。__bool__
:当调用 bool(obj) 时,会调用 __bool__()
方法,返回 True 或 False:当自定义类中没有定义__str__()
和 __repr__()
时,在进行对象的输出时,会调用默认的__str__()
和 __repr__()
;当类中只包含 __str__()
时,调用 print()
或str()
函数进行对象的输出,会调用__str__()
,直接输出调用默认的 __repr__()
;当类中既包含 __str__()
又包含__repr__()
时,调用 print()
或str()
函数进行对象的输出,会调用__str__()
,直接输出会调用__repr__()
;当类中只包含__repr__()
时,调用 print() 或str()函数进行对象的输出和直接输出都会调用 __repr__()
。
因此,对于自定义的类,建议定义__str__和__repr__,以更好的进行交互;其中__str__可以考虑设计为想转换输出的字符串,在后续str(obj)将对象转为特定的字符串输出时提供一些便利;__repr__可以考虑设计为输出较为详细的信息,如列名,甚至包括部分关键参数名,这样在开发的时候便于获取对象的准确信息(如sklearn中的机器学习模块就是如此设计)
这类魔法方法主要再对对象的属性进行访问、定义、修改时起作用。主要有:
__getattr__(self, name)
: 定义当用户试图获取一个属性时的行为。__getattribute__(self, name)
:定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__
)。__setattr__(self, name, value)
:定义当一个属性被设置时的行为。__delattr__(self, name)
:定义当一个属性被删除时的行为。class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __setattr__(self, key, value):
print(key,value)
print('this is magic method setattr')
def __getattr__(self, item):
print('getattr:%s'%item)
print('this is magic method getattr')
def __delattr__(self, item):
print('delattr:%s'%item)
print('this is magic method delattr')
>>>m=A(1,2)
a 1
this is magic method setattr#初始化self.a=a时调用 __setattr__
b 2
this is magic method setattr#初始化self.b=b时调用 __setattr__
>>>a=m.a
getattr:a
this is magic method getattr#访问属性a时调用__getattr__
>>>m.b=100
b 100
this is magic method setattr#修改属性b时调用__setattr__
>>>delattr(m,'a')
delattr:a
this is magic method delattr#删除属性a时调用__delattr__
>>>print(m.a)
getattr:a
this is magic method getattr
None#属性a被删除,为None
在上面代码中,重载了__setattr__
,因此属性初始化时就调用重载后的__setattr__
;但是在初始化属性调用__setattr__
时需要配合实例的属性管理__dict__
来进行,即需要将属性都在self.__dict__
中进行注册,否则实例是访问不到这些属性的。
>>>print(m.a)
getattr:a
this is magic method getattr
None#可以看到,并没有初始化成功为a=1,因为重载的__setattr__方法内部尚未将属性在__dict__中注册
#修改上面的__setattr__:
class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __setattr__(self, key, value):
print(key,value)
print('this is magic method setattr')
self.__dict__[key] = value#在__dict__注册实例属性
#super().__setattr__(key,value) 也可以通过继承的方式来实现;
def __getattr__(self, item):
print('getattr:%s'%item)
print('this is magic method getattr')
def f(self):
return self.__dict__#查看属性管理字典
>>>m=A(1,2)
>>>m.a
1
>>>m.f()
{'a': 1, 'b': 2}
控制属性重载的使用场景:如在初始化属性时先对属性的值进行拦截,进行相应的处理或者判断(比如类型判断,或者范围判断)
class A(object):
def __init__(self,age,sex):
self.age=age
self.sex=sex
def __setattr__(self, key, value):
if key=='age':
if not 0<=value<=100:
raise Exception('age must between 0 and 100')
elif key=='age':
if not (value=='male' or value=='female'):
raise Exception('sex must be male of female')
else:
pass
super().__setattr__(key,value)
>>>m=A(age=102,sex='male')
Exception: age must between 0 and 100
>>>m=A(age=90,sex='hhh')
Exception: sex must be male of female
>>>m=A(age=90,sex='male')
>>>print(m.sex,m.age)
male 90
通过定义各类比较、运算、类型相关的魔法方法,来实现对象之间的各类比较、运算等操作。这类魔法方法非常多,不一一展开。
有一些方法可以自定义容器,就像python内置的list,tuple,dict等等;容器分为可变容器和不可变容器,这里的细节需要去了解相关的协议。如果自定义一个不可变容器的话,只能定义__len__
和__getitem__
;定义一个可变容器除了不可变容器的所有魔法方法,还需要定义__setitem__
和__delitem__
;如果容器可迭代。还需要定义__iter__
__len__(self)
:返回容器的长度
__getitem__(self,key)
:当需要执行self[key]的方式去调用容器中的对象,调用的时该方法
__setitem__(self,key,value)
:当需要执行self[key] = value时,调用的是该方法。
__delitem__(self, key)
:当需要执行 del self[key]时,需要调用该方法;
__iter__(self)
:当容器可以执行 for x in container: ,或者使用iter(container)时,需要定义该方法
__reversed__(self)
:实现当reversed()被调用时的行为。应该返回序列反转后的版本。仅当序列可以是有序的时候实现它,例如对于列表或者元组。
__contains__(self, item)
:定义了调用in和not in来测试成员是否存在的时候所产生的行为。
class SpecialList(object):
def __init__(self,values=None):
if values is None:
self.values=[]
else:
self.values=values
self.count={}.fromkeys(range(len(self.values)),0)
def __len__(self):#通过len(obj)访问容器长度
return len(self.values)
def __getitem__(self, key):#通过obj[key]访问容器内的对象
self.count[key]+=1
return self.values[key]
def __setitem__(self, key, value):#通过obj[key]=value去修改容器内的对象
self.values[key]=value
def __delitem__(self, key):#通过del obj[key]来删除容器内的对象
del self.values[key]
def __iter__(self):#通过for 循环来遍历容器
return iter(self.values)
def __next__(self):
# 迭代的具体细节
# 如果__iter__返回时self 则必须实现此方法
if self._index >= len(self.values):
raise StopIteration()
value = self.values[self._index]
self._index += 1
return value
def __reversed__(self):#通过reverse(obj)来反转容器内的对象
return SpecialList(reversed(self.values))
def __contains__(self, item):#通过 item in obj来判断元素是否在容器内
return item in self.values
def append(self, value):
self.values.append(value)
def head(self):
# 获取第一个元素
return self.values[0]
def tail(self):
# 获取第一个元素之后的所有元素
return self.values[1:]
在Python中,方法也是一种高等的对象。通过对对象实现__call__
就可以实现像调用方法一样去调用类。
class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __call__(self,a):
self.a=a
>>>m=A(1,2)
>>>m.a
1
>>>m.b
2
>>>id(m)
1460475565152
>>>m(100)#像函数一样直接调用类,本质是调用的__call__方法
>>>m.a
100
>>>m.b
2
>>>id(m)
1460475565152
应用场景:
在序列化的时候也是调用的内置魔法方法:
__getstate__()
:用于Python 对象的序列化,指定在序列化时将哪些信息记录下来__setstate__()
:用于Python 对象的反序列化,指定在反序列化时指明如何利用信息class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __getstate__(self):
print('this is magic method __getstate__')
return {'a':self.a,
'b':self.b}#序列化时返回的,即为在反序列化时传入的state
def __setstate__(self, state):
print('this is magic method __setstate__')
self.a=state['a']
self.b=300
import pickle
>>>a=A(1,2)
>>>a_1=pickle.dumps(a)#调用__getstate__
>>>print(a_1)
this is magic method __getstate__
b'\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02ub.'
>>>a_2=pickle.loads(a_1)#调用__setstate__
>>>print(a_2)
this is magic method __setstate__
<__main__.A object at 0x000001BF5B086670>
>>>print(a_2.a,a_2.b)
1 300
希望在后续工程化工作中能够有意识有意义去运用python的魔法函数。