乐趣区

关于python:python魔法方法长文详解

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))#<__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}
<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 new
args:1
kwargs:{'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 init
this 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=n
this is A init
>>>del m#m 被删除,这个对象的援用数为 0;主动调用其 del 办法
this is magic method del
>>>del n
>>>del n1
this 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 init
this 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 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}

其中,重载 __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 datetime
today=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.a
1
>>>m.b
2
>>>id(m)
1460475565152
>>>m(100)# 像函数一样间接调用类,实质是调用的__call__办法
>>>m.a
100
>>>m.b
2
>>>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 call
hhh

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

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

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

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)# 如果未定义,则这两个对象就算外面的属性和办法都雷同,但仍会返回 False
eq
eq# 返回两个应该和 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 属性雷同即返回 True
eq
True
>>>print(x.__eq__(y))
eq
True

再举一个 __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/…

退出移动版