- 前言
- 魔法函数
- 定义
- 常用的魔法方法
- 类构造与初始化
- 类的表示
- 控制属性访问
- 比较、运算等 *** 作
- 容器类 *** 作
- 可调用对象
- 序列化
- 总结
本篇博客主要介绍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'
- object将__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
应用场景:
- 自建一个简单的装饰器(实际例子如 bottle 框架源码的 cached_property)
- 可以用作抽象窗口函数,抽象出使用规则,通过子类的改写其他方法,在不改变原代码的情况下取得不同的结果
在序列化的时候也是调用的内置魔法方法:
- __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'x80x04x95&x00x00x00x00x00x00x00x8cx08__main__x94x8cx01Ax94x93x94)x81x94}x94(x8cx01ax94Kx01x8cx01bx94Kx02ub.' >>>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的魔法函数。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)