Python中常见魔法方法介绍
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python中常见魔法方法介绍相关的知识,希望对你有一定的参考价值。
参考技术A魔法方法 (Magic Methods) 是Python中的内置函数,一般以双下划线开头和结尾,例如__ init__ 、 __del__ 等。之所以称之为魔法方法,是因为这些方法会在进行特定的操作时会自动被调用。
在Python中,可以通过dir()方法来查看某个对象的所有方法和属性,其中双下划线开头和结尾的就是该对象的魔法方法。以字符串对象为例:
可以看到字符串对象有 __add__ 方法,所以在Python中可以直接对字符串对象使用"+"操作,当Python识别到"+"操作时,就会调用该对象的 __add__ 方法。有需要时我们可以在自己的类中重写 __add__ 方法来完成自己想要的效果。
我们重写了 __add__ 方法,当Python识别"+"操作时,会自动调用重写后的 __add__ 方法。可以看到,魔法方法在类或对象的某些事件出发后会自动执行,如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。使用魔法方法,我们可以非常方便地给类添加特殊的功能。
1、构造与初始化
__ new __ 、 __ init __ 这两个魔法方法常用于对类的初始化操作。上面我们创建a1 = A("hello")时,但首先调用的是 __ new __ ;初始化一个类分为两步:
a.调用该类的new方法,返回该类的实例对象
b.调用该类的init方法,对实例对象进行初始化。
__new__ (cls, *args, **kwargs)至少需要一个cls参数,代表传入的类。后面两个参数传递给 __ init __ 。在 __ new __ 可以决定是否继续调用 __ init __ 方法,只有当 __ new __ 返回了当前类cls的实例,才会接着调用 __ init __ 。结合 __ new __ 方法的特性,我们可以通过重写 __ new __ 方法实现Python的单例模式:
可以看到虽然创建了两个对象,但两个对象的地址相同。
2、控制属性访问这类魔法
方法主要对对象的属性进行访问、定义、修改时起作用。主要有:
__getattr__(self, name): 定义当用户试图获取一个属性时的行为。
__getattribute__(self, name):定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用getattr)。
__setattr__(self, name, value):定义当一个属性被设置时的行为。
当初始化属性时如self.a=a时或修改实例属性如ins.a=1时本质时调用魔法方法self. __ setattr __ (name,values);当实例访问某个属性如ins.a本质是调用魔法方法a. __ getattr __ (name)
3、容器类操作
有一些方法可以让我们自己定义自己的容器,就像Python内置的List,Tuple,Dict等等;容器分为可变容器和不可变容器。
如果自定义一个不可变容器的话,只能定义__ len__ 和__ getitem__ ;定义一个可变容器除了不可变容器的所有魔法方法,还需要定义__ setitem__ 和__ delitem__ ;如果容器可迭代。还需要定义__ iter __。
__len__(self):返回容器的长度
__getitem__(self,key):当需要执行self[key]的方式去调用容器中的对象,调用的是该方法
__setitem__(self,key,value):当需要执行self[key] = value时,调用的是该方法
__iter__(self):当容器可以执行 for x in container:,或者使用iter(container)时,需要定义该方法
下面举一个例子,实现一个容器,该容器有List的一般功能,同时增加一些其它功能如访问第一个元素,最后一个元素,记录每个元素被访问的次数等。
这类方法的使用场景主要在你需要定义一个满足需求的容器类数据结构时会用到,比如可以尝试自定义实现树结构、链表等数据结构(在collections中均已有),或者项目中需要定制的一些容器类型。
魔法方法在Python代码中能够简化代码,提高代码可读性,在常见的Python第三方库中可以看到很多对于魔法方法的运用。
因此当前这篇文章仅是抛砖引玉,真正的使用需要在开源的优秀源码中以及自身的工程实践中不断加深理解并合适应用。
Python基础——魔法方法
文章目录
魔法方法
前面在介绍类和对象时,已经接触过Python常用的魔法方法,那么什么是魔法方法呢?
- 魔法方法总是被下划线包围,例如_ _init_ _()。
- 魔法方法是面向对象的Python的一切,如果不知道魔法方法,那么说明你还没意识到面向对象的Python的强大。
- 魔法方法的魔力体现在它们总能够在适当的时候被调用。
构造和析构
这里主要是_ _init_ _()、_ _new_ _()和_ _del_ _()方法,这些方法均在前面的Python基础——类和对象中详细讲解过,此处便不再赘述。
算数运算
现在来讲一个新的名词——工厂函数 。Python2.2以后,对类和类型进行了统一,做法就是将int()、float()、str()、list()、tuple()这些BIF转换为工厂函数。
如上图所示,普通的BIF应该是<class ‘builtin_function_or_method’>,而工厂函数则是<class ‘type’>。有没有觉得<class ‘type’>很眼熟?通过我们前面的学习,我们知道,如果定义一个类:
其类型也是type类型,也就是类对象。其实所谓的工厂函数,其实就是一个类对象。当你调用它们的时候,事实上就是创建一个相应的实例对象:
对象是可以计算的!Python中无处不对象,当在求a+b等于多少的时候,事实上Python就是将两个对象进行相加操作。Python的魔法方法还提供了自定义对象的数值处理,通过下面这些魔法方法的重写,可以自定义任何对象间的算数运算。
算数操作符
下表列举了算数运算相关的魔法方法。
魔法方法 | 含义 |
---|---|
_ _add_ _(self, other) | 定义加法的行为:+ |
_ _sub_ _(self, other) | 定义减法的行为:- |
_ _mul_ _(self, other) | 定义乘法的行为:* |
_ _truediv_ _(self, other) | 定义真除法的行为:/ |
_ _floordiv_ _(self, other) | 定义整数除法的行为:// |
_ _mod_ _(self, other) | 定义取模算法的行为:% |
_ _divmod_ _(self, other) | 定义当被divmod()调用时的行为 |
_ _pow_ _(self, other[, modulo]) | 定义当被power()调用或**运算时的行为 |
_ _lshift_ _(self, other) | 定义按位左移位的行为:<< |
_ _rshift_ _(self, other) | 定义按位右移位的行为:>> |
_ _and_ _(self, other) | 定义按位与操作的行为:& |
_ _xor_ _(self, other) | 定义按位异或操作的行为:^ |
_ _or_ _(self, other) | 定义按位或操作的行为:| |
举个例子,下面定义一个比较特立独行的类:
有些读者可能会问,我想自己写代码,不想通过Python的默认方案行不行?答案是可以的,但是要小心。
有些人可能会像上图那样写,这样的结果就是会陷入无限递归。为什么会这样呢?
这是因为当对象涉及加法操作时,会自动调用魔法方法_ _add_ _(),但是看看上面的魔法方法写的是return self + other,也就是返回对象本身加另外一个对象,这就又自动触发调用_ _add_ _()方法了,这样就形成了无限递归。所以需要修改成下面这样:
上面介绍了很多有关算数运算的魔法方法,意思是当对象进行了相关的算数运算,自然就会触发对应的魔法方法。
反运算
下表列举了反运算相关的魔法方法。
魔法方法 | 含义 |
---|---|
_ _radd_ _(self, other) | 定义加法的行为:+(当左操作数不支持相应的操作时被调用) |
_ _rsub_ _(self, other) | 定义减法的行为:-(当左操作数不支持相应的操作时被调用) |
_ _rmul_ _(self, other) | 定义乘法的行为:*(当左操作数不支持相应的操作时被调用) |
_ _rtruediv_ _(self, other) | 定义真除法的行为:/(当左操作数不支持相应的操作时被调用) |
_ _rfloordiv_ _(self, other) | 定义整数除法的行为://(当左操作数不支持相应的操作时被调用) |
_ _rmod_ _(self, other) | 定义取模算法的行为:%(当左操作数不支持相应的操作时被调用) |
_ _rdivmod_ _(self, other) | 定义当被divmod()调用时的行为(当左操作数不支持相应的操作时被调用) |
_ _rpow_ _(self, other[, modulo]) | 定义当被power()调用或**运算时的行为(当左操作数不支持相应的操作时被调用) |
_ _rlshift_ _(self, other) | 定义按位左移位的行为:<<(当左操作数不支持相应的操作时被调用) |
_ _rrshift_ _(self, other) | 定义按位右移位的行为:>>(当左操作数不支持相应的操作时被调用) |
_ _rand_ _(self, other) | 定义按位与操作的行为:&(当左操作数不支持相应的操作时被调用) |
_ _rxor_ _(self, other) | 定义按位异或操作的行为:^(当左操作数不支持相应的操作时被调用) |
_ _ror_ _(self, other) | 定义按位或操作的行为:|(当左操作数不支持相应的操作时被调用) |
这里的反运算魔法方法跟上节介绍的算数运算符保持一一对应,不同之处就是反运算的魔法方法多了一个r,举个例子:
尝试一下:
关于反运算,这里还需要注意一点:对于a + b,b的_ _radd_ _(self, other)的self是b对象,other是a对象,所以不能这么写:
所以对于注重操作数顺序的运算符(例如减法、除法、移位),在重写反运算魔法方法的时候,就一定要注意顺序问题。
增量赋值运算
Python也有大量的魔法方法可以定制增量赋值语句,增量赋值其实就是一种偷懒的形式,它将操作符与赋值结合起来,例如:
一元操作符
一元操作符就是只有一个操作数的意思,像 a + b 这样,加号左右有a、b两个操作数,叫作二元操作数。一元操作符如-,其作为负号的时候,表示负数,其就是一元操作符,如-1。
Python支持的一元操作符主要有_ _neg_ _()(表示正号行为),_ _pos_ _()(定义负号行为),_ _abs_ _()(定义当被abs()调用时的行为,就是取绝对值的意思),还有_ _invert_ _()(定义按位取反的行为)。
属性访问
通常可以通过点(.)操作符的形式去访问对象的属性,例如:
此外,关于属性访问,也有相应的魔法方法来管理,下表列举了属性相关的魔法方法。
魔法方法 | 含义 |
---|---|
_ _getattr_ _(self, name) | 定义当用户试图获取一个不存在的属性时的行为 |
_ _getattribute_ _(self, name) | 定义当该类的属性被访问时的行为 |
_ _setattr_ _(self, name, value) | 定义当一个属性被设置时的行为 |
_ _delattr_ _(self, name) | 定义当一个属性被删除时的行为 |
举个例子:
这几个魔法方法在使用上需要注意的是,有一个死循环陷阱,初学者比较容易中招,通过下面这个实例来讲解一下。
写一个矩形类(Rectangle),默认有宽(width)和高(height)两个属性,如果为一个叫square的属性赋值,那么说明这是一个正方形,其值就是正方形的边长,此时宽和高都等于边长。
这是为什么呢?
分析一下:实例化对象,调用_ _init_ _()方法,在这里给self.width和self.height分别初始化赋值。一发生赋值操作,就会自动触发_ _setattr_ _()魔法方法,width和height两个属性被赋值,于是执行else的下边的语句,就又变成了self.width=value,那么就相当于又触发了_ _setattr_ _()魔法方法了,死循环陷阱就是这么来的。
那怎么解决呢?
一种方法就是跟刚才一样,用super()来调用基类的_ _setattr_ _(),那么这样就依赖基类的方法来实现赋值:
另一种方法就是给特殊属性_ _dict_ _赋值。 对象有一个特殊的属性,叫作_ _dict_ _,它的作用就是以字典的形式显示出当前对象的所有属性以及相对应的值:
以上是关于Python中常见魔法方法介绍的主要内容,如果未能解决你的问题,请参考以下文章