python 魔法函数

前言

本篇博客主要介绍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__方法使用如下:

  1. __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}
<class '__main__.A'>
<__main__.A object at 0x000001BCD98FB3D0>
this is A init#再进入__init__
<__main__.A object at 0x000001D0BC3EB3D0>#self就是__new__返回的对象实例
  1. __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}
<class '__main__.A'>
>>>print(m.a)#报错,未进入到当前类的__init__进行初始化
AttributeError: 'NoneType' object has no attribute 'a'
  1. object将__new__()方法定义为静态方法,并且至少需要传递一个参数cls,cls表示需要实例化的类,此参数在实例化时由Python解释器自动提供。
  2. __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

比较、运算等操作

通过定义各类比较、运算、类型相关的魔法方法,来实现对象之间的各类比较、运算等操作。这类魔法方法非常多,不一一展开。

  1. 用于比较的魔法函数:
    在这里插入图片描述
  2. 双目运算符或函数
    在这里插入图片描述
  3. 增量运算
    在这里插入图片描述
  4. 类型转换
    在这里插入图片描述

容器类操作

有一些方法可以自定义容器,就像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

应用场景:

  1. 自建一个简单的装饰器(实际例子如 bottle 框架源码的 cached_property)
  2. 可以用作抽象窗口函数,抽象出使用规则,通过子类的改写其他方法,在不改变原代码的情况下取得不同的结果

序列化

在序列化的时候也是调用的内置魔法方法:

  • __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的魔法函数。

  • 20
    点赞
  • 178
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值