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 len2

能够看到,魔术办法在类或对象的某些事件登程后会主动执行,如果心愿依据本人的程序定制非凡性能的类,那么就须要对这些办法进行重写。应用这些「魔法办法」,咱们能够十分不便地给类增加非凡的性能。

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))#<__main__.A object at 0x000001BCD98FB3D0>,一个A的对象实例        return object.__new__(cls)#创立实例并返回>>>a=A(1,b=10)this is A new#先进入__new__args:1kwargs:{'b': 10}<class '__main__.A'><__main__.A object at 0x000001BCD98FB3D0>this is A init#再进入__init__<__main__.A object at 0x000001D0BC3EB3D0>#self就是__new__返回的对象实例

其中object是所有类的基类,object.__new__则返回以后传入类cls的实例对象;以后类的__new__返回以后类的实例对象后,再进入__init__对实例进行初始化。

如果 __new__未返回以后类的实例,则不会进入以后类的__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 newargs:1kwargs:{'b': 10}<class '__main__.A'>>>>print(m.a)#报错,未进入到以后类的__init__进行初始化AttributeError: 'NoneType' object has no attribute 'a'

进一步,__new__返回的是其它实例,以后实例的__init__也不会调用

class A(object):    def __init__(self,a):        print('this is A init')        self.a=a    def __new__(cls, *args, **kwargs):        print('this is A new')        print(cls)        print(object.__new__(cls))        return object.__new__(cls)class B(A):    def __init__(self, a):        super().__init__(a)        print('this is B init')    def __new__(cls ,*args, **kwargs):        print('this is B new')        print(cls)        return super().__new__(A, *args, **kwargs)#这里返回的是A的实例对象<__main__.A object at 0x000002A08683B370>,因而不会调用B的__init__;>>>m=B(1)#初始化过程中,调用的程序为:子类B的__new__》父类A的__new__,调用Object.__new__返回A的实例对象》返回到子类B的__new__中,返回A对象的实例,不是B的对象实例,因而不再调用B的__init__this is B new<class '__main__.B'>this is A new<class '__main__.A'><__main__.A object at 0x000002A08683B370>#批改子类的__new__:class B(A):    def __init__(self, a):        super().__init__(a)        print('this is B init')    def __new__(cls ,*args, **kwargs):#cls默认代表传入        print('this is B new')        print(cls)        return super().__new__(cls, *args, **kwargs)#即是通过super()调用父类object的初始__new__办法来创立实例。返回B的对象实例>>>m=B(1)this is B new<class '__main__.B'>this is A new<class '__main__.B'><__main__.B object at 0x00000167C541B370>this is A initthis is B init#调用程序:# 调用B的__new__》传入B对象,调用A的__new__,调用object.__new__,返回B的实例》返回到B的__new__中》调用B的__init__》调用父类A的__init__,初始化并继承A的属性》持续B的__init__属性重写;

__new__的应用场景如单例模式、工厂模式,以及一些不可变对象的继承上。这类利用十分值得关注并应用,能够大大的让代码看起来柔美和简洁;这里不再开展。

__del__办法则是当对象被零碎回收的时候调用的魔法办法,在对象生命周期调用完结时调用该办法。Python 采纳主动援用计数(ARC)形式来回收对象所占用的空间,当程序中有一个变量援用该 Python 对象时,Python 会主动保障该对象援用计数为 1;当程序中有两个变量援用该 Python 对象时,Python 会主动保障该对象援用计数为 2,依此类推,如果一个对象的援用计数变成了 0,则阐明程序中不再有变量援用该对象,表明程序不再须要该对象,因而 Python 就会回收该对象。所以大部分时候,都不须要咱们手动去删掉不再应用的对象,python的回收机制会主动帮咱们做这件事。

class A(object):    def __init__(self,a):        print('this is A init')        self.a=a    def __del__(self):        print('this is magic method del')>>>m=A(1)this is A init>>>n=A(2)>>>n1=nthis is A init>>>del m#m被删除,这个对象的援用数为0;主动调用其del办法this is magic method del>>>del n>>>del n1this is magic method del#进一步,查看python的主动回收机制,不必del去手动删除实例def func(a,b):    x=A(a).a    y=b    return x+y>>>func(1,2)this is A initthis is magic method del#因为函数调用返回后,x和y主动被销毁;因而会触发__del__3

2.2 管制属性拜访

这类魔法办法次要再对对象的属性进行拜访、定义、批改时起作用。次要有:

  • __getattr__(self, name): 定义当用户试图获取一个属性时的行为。
  • __getattribute__(self, name):定义当该类的属性被拜访时的行为(先调用该办法,查看是否存在该属性,若不存在,接着去调用__getattr__)。
  • __setattr__(self, name, value):定义当一个属性被设置时的行为。
  • __delattr__(self, name):定义当一个属性被删除时的行为。

当初始化属性时如self.a=a时或批改实例属性如ins.a=1时实质时调用魔法办法self.__setattr__(name,values);当实例拜访某个属性如ins.a实质是调用魔法办法a.__getattr__(name);当删除对象某个属性如delattr(ins,'a')实质是调用魔法办法a.__delattr__(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 1this is magic method setattr#初始化self.a=a时调用 __setattr__b 2this is magic method setattr#初始化self.b=b时调用 __setattr__>>>a=m.agetattr:athis is magic method getattr#拜访属性a时调用__getattr__>>>m.b=100b 100this is magic method setattr#批改属性b时调用__setattr__>>>delattr(m,'a')delattr:athis is magic method delattr#删除属性a时调用__delattr__>>>print(m.a)getattr:athis is magic method getattrNone#属性a被删除,为None

下面的代码有一些隐患,因为咱们重载了__setattr__,因而属性初始化时就调用重载后的__setattr__;然而在初始化属性调用__setattr__时须要配合实例的属性治理__dict__来进行,即须要将属性都在self.__dict__中进行注册,否则实例是拜访不到这些属性的。如

>>>print(m.a)getattr:athis is magic method getattrNone#能够看到,并没有初始化胜利为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.a1>>>m.f(){'a': 1, 'b': 2}

其中,重载__setattr__时肯定要配合__dict__,或者在增加了自定义的性能后,通过继承来实现该办法,不然容易陷入无线递归的陷阱。

管制属性重载的应用场景:如在初始化属性时先对属性的值进行拦挡,进行相应的解决或者判断(比方类型判断,或者范畴判断);

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

其它管制属性拜访的魔法办法当前遇到了比拟有领会后再补充。

2.3 容器类操作

有一些办法能够让咱们本人定义本人的容器,就像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来测试成员是否存在的时候所产生的行为。

上面举一个例子,实现一个容器,该容器有List的个别性能,同时减少一些其它性能如拜访第一个元素,最初一个元素,记录每个元素被拜访的次数等

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 __getitem__(self, key):#通过obj[key]拜访容器内的对象        self.count[key]+=1        return self.values[key]    >>>a=SpecialList()>>>print(len(a))#未定义魔法办法__len__,因而无奈通过len()调用TypeError: object of type 'SpecialList' has no len()    >>>b=SpecialList([1,2,3])>>>print(b[1])#调用__getitem__办法2

上面是残缺例子:

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:]

这类办法的应用场景次要在你须要定义一个满足需要的容器类数据结构时会用到,比方能够尝试自定义实现树结构、链表等数据结构(在collections中均已有),或者我的项目中须要定制的一些容器类型。

2.4 类的示意

类的示意相干的魔法办法次要有__str____repr____bool__

  • __str__次要是在打印对象print(obj)时,会隐式调用str(obj),即调用类中的__str__办法;定了该办法就能够通过str(obj)来调用;
  • __repr__次要式在间接输入对象时的显示,会调用__repr__办法;定义了该办法就能够通过repr(obj)来调用。

这两个办法都是将类显示为字符串;

举例(以datetime模块中的date对象举例):

import datetimetoday=datetime.date.today()>>>print(today)#调用date类的__str__办法来显示2021-07-04>>>print(str(today))2021-07-04>>>today#调用date类的__repr__来显示datetime.date(2021, 7, 4)

回溯到date类的源码,能够清晰的对下面的输入进行解释:

   #date类中的__repr__和__str__办法定义:     def __repr__(self):        """Convert to formal string, for repr().        >>> dt = datetime(2010, 1, 1)        >>> repr(dt)        'datetime.datetime(2010, 1, 1, 0, 0)'        >>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)        >>> repr(dt)        'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'        """        return "%s.%s(%d, %d, %d)" % (self.__class__.__module__,                                      self.__class__.__qualname__,                                      self._year,                                      self._month,                                      self._day)    def isoformat(self):        """Return the date formatted according to ISO.            This is 'YYYY-MM-DD'.            References:            - http://www.w3.org/TR/NOTE-datetime            - http://www.cl.cam.ac.uk/~mgk25/iso-time.html            """        return "%04d-%02d-%02d" % (self._year, self._month, self._day)    __str__ = isoformat

如果自定义类中不定义这两个魔法办法,则会用父类或者object类的默认示意办法,举例:

class A(object):    def __init__(self, a):        self.a = a>>>a = A(1)>>>print(a)#调用__str__<__main__.A object at 0x000001540B1F4DF0>>>>a#调用__repr__<__main__.A object at 0x000001540B1F4DF0>>>>str(a)#调用__str__'<__main__.A object at 0x000001540B1F4DF0>'>>>print(str(a))#调用__str__<__main__.A object at 0x000001540B1F4DF0>

能够看到,如果没有定义__str____repr__,则会调用默认的魔法办法(object的__str____repr__)即obj.__class__ object at XXX;

只定义了__str__:

class A(object):    def __init__(self,a):        self.a=a    def __str__(self):        return '(%s,%s)'%(self.__class__.__name__,self.a)>>>a=A(1)>>>print(a)(A,1)>>>a#未定义__repr__,仍旧调用默认的__repr__办法<__main__.A object at 0x000001540B215790>

只定义了__repr__

class A(object):    def __init__(self, a):        self.a = a    # def __str__(self):    #     return '(%s,%s)'%(self.__class__.__name__,self.a)    def __repr__(self):        return '(repr:%s,%s)' % (self.__class__.__name__, self.a)>>>a = A(1)>>>a#调用__repr__(repr:A,1)>>>print(a)#未定义__str__;转为调用__repr__;(repr:A,1)>>>str(a)#未定义__str__;转为调用__repr__;'(repr:A,1)'

从下面能够看出,当自定义类中没有定义 __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中的机器学习模块就是如此设计)

  • __bool__:当调用 bool(obj) 时,会调用 __bool__() 办法,返回 True 或 False:
class A(object):    def __init__(self,a):        self.a=a    def __bool__(self):        return self.a>10>>>a=A(1)>>>bool(a)False>>>b=A(100)>>>bool(b)True

如果未定义__bool__,则会调用内置的bool对象。

2.5 可调用对象

在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.a1>>>m.b2>>>id(m)1460475565152>>>m(100)#像函数一样间接调用类,实质是调用的__call__办法>>>m.a100>>>m.b2>>>id(m)1460475565152

__call__办法的一些应用场景:

自建一个简略的装璜器(理论例子如 bottle 框架源码的 cached_property):

class A(object):    def __init__(self,func):        self.func=func    def __call__(self):        print('this is call')        return self.func()>>>@A   #语法糖,相当于A(f)>>>def f():    print('hhh')>>>f()this is callhhh

定义一个对象,对象中具备各类办法,然而外围性能只有一个,能够通过可调用对象来简略且间接的调用,不必每次都实例化对象。

同样还能够用作形象窗口函数,形象出应用规定,通过子类的改写其余办法,在不扭转原代码的状况下获得不同的后果,比方:

class CSVDataProcess:    def __init__(self):    def example_one(self):        pass        def __call__(self, exmaple):        try:            target_func = getattr(self, exmaple)            target_func()        except Exception as e:            print("execute error", e)if __name__ == "__main__":    csv = CSVDataProcess()    csv("example_one")#这个例子在子类继承时,子类从新实现example;通过调用子类间接就能够通过__call__来间接调用该子类的example办法;整个代码十分简洁了;

2.6 序列化

python中有一个pickle模块来对实例化对象进行序列化;如pickle.loads(obj),pickle.dumps(obj)等;在序列化的时候也是调用的内置魔法办法:

  • __getstate__():用于Python 对象的序列化,指定在序列化时将哪些信息记录下来
  • __setstate__():用于Python 对象的反序列化,指定在反序列化时指明如何利用信息

举例说明:

#间接通过pickle保留一个对象实例,默认将该实例的属性、办法都保留下来;class A(object):    def __init__(self,a,b):        self.a=a        self.b=bimport pickle>>>a=A(1,2)>>>a_1=pickle.dumps(a)>>>print(a_1)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)>>>print(a_2)<__main__.A object at 0x000001BF5B086670>>>>print(a_2.a,a_2.b)1 2

如果重写__getstate__()__setstate__(),如下:

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=300import 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

2.7 反射

咱们能够管制怎么应用内置在函数sisinstance()和issubclass()办法 反射定义魔术办法. 这个魔术办法是:

  • __instancecheck__(self, instance)
  • __subclasscheck__(self, subclass)

这两个办法定义在元类中在有意义,暂不开展。

2.8 比拟、运算、类型等操作

通过定义各类比拟、运算、类型相干的魔法办法,来实现对象之间的各类比拟、运算等操作。这类魔法办法十分多,不一一开展,以__eq__ 魔法办法举例说明,如果自定义的对象要实现==的比拟性能,则必须在类中实现__eq__

#__eq__ 办法,能够判断两个对象是否相等class A(object):    def __init__(self,a,b):        self.a=a        self.b=b    def __eq__(self, other):#这里并没有重写,只是加了一句Print来监控;还是调用的Objecgt的__eq__        print('eq')        return super().__eq__(other)>>>x=A(1,2)>>>y=A(1,2)>>>print(x == y)#如果未定义,则这两个对象就算外面的属性和办法都雷同,但仍会返回Falseeqeq#返回两个应该和x与y都调用了无关False>>>print(x.__eq__(y))#单向x调用;因为最终仍是调用object的__eq__NotImplemented

自定义__eq__:

class A(object):    def __init__(self,a,b):        self.a=a        self.b=b    def __eq__(self, other):        print('eq')        if self.a==other.a:            return True        else:            return False>>>x=A(1,2)>>>y=A(1,3)>>>print(x == y)#只有两个实例的a属性雷同即返回TrueeqTrue>>>print(x.__eq__(y))eqTrue

再举一个__int__的类型转换的魔法办法

class A(object):    def __init__(self,a,b):        self.a=a        self.b=b>>>x=A(1,2)>>>int(x)#未定义__int__,就只能按默认的int对象来转换,外面必须为string,float这类;TypeError: int() argument must be a string, a bytes-like object or a number, not 'A'        class A(object):    def __init__(self,a,b):        self.a=a        self.b=b    def __int__(self):        return int(self.a[:3])#必须返回为int数字>>>x=A('345_hhh',3)>>>print(int(x))345

其它的比拟、运算、类型等魔法办法如下:

  1. 用于比拟的魔法办法

  1. 双目运算符或函数

  1. 增量运算

  1. 类型转换

总结

这篇文章较为具体的论述了python中的各类魔法办法及相干demo;但这类办法在python的文档中较为扩散,最重要的是鲜有理论的工程示例。但各类办法只有有理论的案例中才会真正领会到其“魔法”之处,比方大大简化代码,进步代码可读性、健壮性等等。在python一些第三方库中,查其源码都能够见到十分多的魔法办法的理论利用,因而以后这篇文章仅是抛砖引玉,真正的应用须要在开源的优良源码中以及本身的工程实际中一直加深了解并适合利用。

局部参考:

https://blog.csdn.net/weixin_...

https://zhuanlan.zhihu.com/p/...

https://zhuanlan.zhihu.com/p/...

https://zhuanlan.zhihu.com/p/...