“师傅领进门,修行在个人”
本帖子在于让大家快速知道魔术方法是怎么个情况,同时又不让介绍魔术方法又变得太过与抽象。所以本文主要通过例子去介绍魔术方法,并且着重介绍几个类别的魔术方法,但不会追求面面俱到,把所有魔术方法都介绍一遍。学习这种事情,用到的时候再查也是一种策略。
以下是我们常见的一个类的例子:
class dog:
def __init__(self, name , age):
self.name = "tom"
self.age = 5
def speak(self, words = "wo, wo, wo"):
print(words)
def jump(self, ):
pass
这个类定义一条狗,对这个类进行实例化之后,Python会自动首先执行类里面的__init__(self, name, age) 方法,对吧。不信? 你debug试试。这里的__init__()方法其实就是魔术方法之一。
所谓魔术方法就是在类中的,在python中预定义了的名称的方法。这些预定义名称都是在python中约定好了的,被占用了名称的方法。例如最常见的__init__()方法,对吧。你如果指定了一个方法叫这个名字,那python默认这就是初始化功能的方法,会在实例化过程自动首先执行。
在python里面还有许多带双下划线的名称已经被python内部预订好了。这些名字只能做特定一类功能实现的方法名字。
注意,我下面写的a, b 全是你要定义的类名称,非一般的变量名。
例如__format__()
当使用formation(a)函数的时候会自动调用a这个类中的__format__方法
__str__()
当使用例如pycharm这种IDE工具去执行print(a)这个函数的时候,会自动调用a类中的__str__方法,当然,前提是你已经在a中写好了这个方法
__add__()
当使用a + b这种语句的时候,python会自动在 + 符号的左侧的a类中调用a的__add__(self, other)方法, 这里的other参数就是等待被传入的 b
__sub__()
当使用a - b这种语句的时候,python会自动在 - 符号的左侧的a类中调用a的__sub__(self, other)方法, 这里的other参数就是等待被传入的 b
__mul__()
当使用a * b这种语句的时候,python会自动在 * 符号的左侧的a类中调用a的__mul__(self, other)方法, 这里的other参数就是等待被传入的 b
__floordiv__()
当使用a // b这种语句的时候,python会自动在 // 符号的左侧的a类中调用a的__floordiv__(self, other)方法, 这里的other参数就是等待被传入的 b。这个方法就是将除法的结果向下取整数。
不仅仅是一批上面的这种算术运算符方法,还有一些事比较用途的方法。如
__eq__()
当使用a == b这种语句的时候,python会自动在 == 符号的左侧的a类中调用a的__eq__(self, other)方法, 这里的other参数就是等待被传入的 b
__gt__()
大于测试方法。使用>运算符时调用该方法。根据对称性规则,如果你只需要与同类的对象进行比较,则不需要实现此方法。
以上都是一些方法的列举,方法有很多,可以查一些资料。
以上的这些方法,会在使用特殊函数等操作于某个类的时候,这某个类中的特定的魔术方法会被自动调用。例如这里我定义了一个point类。
class dog: def __init__(self,d): self.d = d def __repr__(self): # 这个魔术方法仅在用IDLE窗口中交互时,才会调用这个方法。 return 'dog(' + str(self.d) + ')' def __gt__(self, other): if type(other) == dog: return self.d > other.d else: return self.d > other def __lt__(self, other): if type(other) == dog: return self.d < other.d else: return self.d < other
def __str__(self): s = "tom" return s
先别管这个类里面的__lt__等你不太熟悉的方法。看看这个__str__()的方法,这个方法就是在运行print(dog(5))这种语句的时候,python会自动调用dog中的__str__()方法,从而打印出“tom”。
好了,看完这个打印实例化的类的例子之后,我们再看一些例子。就是将整数与类进行排序。有的人肯定会好奇,整数与类怎么排序呢?首先,理解这一点的关键在于,整数在python中是对象,类在python中也是对象,一切皆对象嘛!第二点关键在于,当使用sort()这个函数的时候,python会自动调用dog类中的__lt__()和__gt__()方法来比较dog类中能够与整数发生比较的“量”与整数的大小关系。例如上述的__lt__()方法中,会将dog类中的实例变量self.d与整数进行比较。
好了,下面就是例举的代码和运行结果。最好你自己去debug一下,印象深刻。
d1, d5, d10 = dog(1), dog(5), dog(10) a_list = [50, dog(5), 100, dog(1), -20, dog(10), 3] a_list.sort() print(a_list)
>>[-20, dog(1), 3, dog(5), dog(10), 50, 100]
好了,到了这里,以上的内容就是给你一个快速的,在整体上对魔术方法有所了解的介绍。对于目前只想有个大概了解的你,可以先不用往下看,以后有时间你再看也不迟。下面的内容是对魔术方法进行更加详细的分类,以及各种类别的魔术方法是如何运行的。内中会牵涉到一些针对特定问题的特定内容。例如反向方法,有点懵没事,后面你就懂了。
比较方法
这些魔术方法是在遇到>,<, >=, <=,==这样的符号的时候,会被自动调用。在一个类中至少应该实现相等比较的方法。
我不想像一般的书籍那样先对一个一个的魔术方法区用文字介绍,或者列一个表格集中介绍。这样的话,对于新手来说,会让人懵逼,效率很低。我先列出一个类的例子。然后再类中的各个方法里面用注释来介绍,这样好理解一些。
class dog: def __init__(self, n): self.n = n def __cmp__(self, other): """ 该方法已经被python3.0或者更高版本丢弃了。在2.0版本中的方法, 这个方法是通过返回-1、0或者1来分别实现小于、等于、大于的功能的 """ pass def __eq__(self, other): """ 使用==运算符时候,会自动调用这个方法来测试两边内容是否相等 方法返回True或者False 如果没有在你的类里面定义这个方法,则python会使用is运算符 """ return self.n == other.n def __ne__(self, other): """ 在使用!=运算符的时候,python调用该方法测试内容是否不相等。 该方法返回True或者False。如果没有你在你的类里面没有定义这个方法, python将会自动调用__eq__方法,然后取结果的相反值。因此,一般情况下, 实现__eq__方法就足够了。 """ return self.n != other.n def __lt__(self, other): """ 当使用<运算符号时候,python调用这个方法。 如果只用于对包含同类对象的集合进行排序,实现本方法就足够了。 如果要对包含不同类对象的集合进行排序,还需要实现__gt__方法。 """ return self.n < other.n def __gt__(self, other): """ 当使用<运算符号时候,python调用这个方法。 如果只用于对包含同类对象的集合进行排序,则不需要这个方法。 如果要对包含不同类对象的集合进行排序,一定要这个方法。后面我会举一个计算例子让大家看到 """ return self.n > other.n def __le__(self, other): """ 使用<=运算符时候,调用该方法。即使实现了__eq__和__lt__方法,也不会自动提供__le__方法。 原则上,每个比较运算符都必须在类中分别实现。 不过对于相同类对象进行比较,即使没有定义这个方法,如果有定义了后面的__ge__方法, 则可以通过__ge__方法来获得__le__方法同等的效果。 """ return self.n <= other.n def __ge__(self, other): """ 使用>=运算符时候,调用该方法。 即使实现了__eq__和__gt__方法,也不会自动提供__ge__方法。 原则上,每个比较运算符都必须在类中分别实现。 不过,对于相同类对象进行比较,即使没有定义这个方法,如果有定义了前面的__le__方法, 则可以通过__le__方法来获得__le__方法同等的效果。 说白了,如果对于同一个类,__le__和__ge__只要有一个就行了。 """ return self.n >= other.n
运行下面的打印
print(dog(2) < dog(3)) print(dog(2) > dog(3)) print(dog(2) == dog(3)) print(dog(2) != dog(3))
得结果
True
False
False
True
好了,为了能够使得dog类能够直接和整数或者浮点数进行比较,可以把dog改造成如下的类。这个类和上面的dog类的区别在于添加了一些if判断语句,判断作比较的另外一个对象是否和dog是同一类型的对象,不是的话,就直接用dog内部的实例变量与整数或者浮点数进行比较。
class dog: def __init__(self, n): self.n = n def __cmp__(self, other): """ 该方法已经被python3.0或者更高版本丢弃了。在2.0版本中的方法, 这个方法是通过返回-1、0或者1来分别实现小于、等于、大于的功能的 """ pass def __eq__(self, other): """ 使用==运算符时候,会自动调用这个方法来测试两边内容是否相等 方法返回True或者False 如果没有在你的类里面定义这个方法,则python会使用is运算符 """ if isinstance(other, dog): return self.n == other.n else: return self.n == other def __ne__(self, other): """ 在使用!=运算符的时候,python调用该方法测试内容是否不相等。 该方法返回True或者False。如果没有你在你的类里面没有定义这个方法, python将会自动调用__eq__方法,然后取结果的相反值。因此,一般情况下, 实现__eq__方法就足够了。 """ return self.n != other.n def __lt__(self, other): """ 当使用<运算符号时候,python调用这个方法。 如果只用于对包含同类对象的集合进行排序,实现本方法就足够了。 如果要对包含不同类对象的集合进行排序,还需要实现__gt__方法。 """ if isinstance(other, dog): return self.n < other.n else: return self.n < other def __gt__(self, other): """ 当使用<运算符号时候,python调用这个方法。 如果只用于对包含同类对象的集合进行排序,则不需要这个方法。 如果要对包含不同类对象的集合进行排序,一定要这个方法。后面我会举一个计算例子让大家看到 """ if isinstance(other, dog): return self.n > other.n else: return self.n > other def __le__(self, other): """ 使用<=运算符时候,调用该方法。即使实现了__eq__和__lt__方法, 也不会自动提供__le__方法。 原则上,每个比较运算符都必须在类中分别实现。 不过对于相同类对象进行比较,即使没有定义这个方法,如果有定义了后面的__ge__方法, 则可以通过__ge__方法来获得__le__方法同等的效果。 """ if isinstance(other, dog): return self.n <= other.n else: return self.n <= other def __ge__(self, other): """ 使用>=运算符时候,调用该方法。 即使实现了__eq__和__gt__方法,也不会自动提供__ge__方法。 原则上,每个比较运算符都必须在类中分别实现。 不过,对于相同类对象进行比较,即使没有定义这个方法,如果有定义了前面的__le__方法, 则可以通过__le__方法来获得__le__方法同等的效果。 说白了,如果对于同一个类,__le__和__ge__只要有一个就行了。 """ if isinstance(other, dog): return self.n >= other.n else: return self.n >= other
打印一些结果
print(dog(2) < 3) print(dog(2) <= 3) print(dog(2) >= 3)
结果:
True
True
False
好了,我们试着用一下sort()函数来对一个既包含整数,也包含dog实例化类的列表进行排序。
d5,d1,d10 = dog(5), dog(1), dog(10) a_list = [50,d5, 100, d1, -20, d10, 3] a_list.sort() print(a_list)
结果是
[-20, <__main__.dog at 0x1a17741ce20>, # 就是dog(5) 3, <__main__.dog at 0x1a17741cd90>, # 就是dog(1) <__main__.dog at 0x1a17741cee0>, # 就是dog(10) 50, 100]
算术运算符方法
我还是通过写出一个类作为例子来说明这种魔术方法
class point: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): """ 加法。当出现“+”符号的时候,python会自动调用此方法。 other就是对右侧操作数的引用 没有这个方法的时候,就会自动调用__radd__方法 """ newx = self.x + other.x newy = self.y + other.y return point(newx, newy) def __sub__(self, other): """ 类似上述描述,你懂的 """ dx = self.x - other.x dy = self.y - other.y return (dx ** 2 + dy ** 2) ** 0.5 def __mul__(self, n): """ 类似上述加法描述,你懂的 """ newx = self.x * n newy = self.y * n return point(newx, newy) def __str__(self): """ 为了打印方便,这里定义一个能够被print调用的方法 没有这个方法的时候,pthon会自动调用下面的__repr__方法 你可以自己去掉这个方法试试 """ s = str(self.x) + ', ' + str(self.y) return s def __repr__(self): s = "point(" + str(self.x) + ', ' + str(self.y) + ')' return s
运行下面代码
pt1 = point(10, 15) pt2 = point(0, 5) x = pt1 + pt2 print(x)
出现一下结果
>>10, 20
其实,算术运算符方法不止加法,减法,乘法等,还有向下取整数__floordiv__(), 普通除法__truediv__(), 求除数和余数__divmod__, 幂运算__pow__.
好了,下面介绍一元算术方法
一元算术方法
同样地,还是举例子,不过只是在上面的point类里面加入一些一元算术方法而已。如下,是更加完整的类。
class point: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): """ 加法。当出现“+”符号的时候,python会自动调用此方法。 other就是对右侧操作数的引用 没有这个方法的时候,就会自动调用__radd__方法 """ newx = self.x + other.x newy = self.y + other.y return point(newx, newy) def __sub__(self, other): """ 类似上述描述,你懂的 """ dx = self.x - other.x dy = self.y - other.y return (dx ** 2 + dy ** 2) ** 0.5 def __mul__(self, n): """ 类似上述加法描述,你懂的 """ newx = self.x * n newy = self.y * n return point(newx, newy) def __str__(self): """ 为了打印方便,这里定义一个能够被print调用的方法 没有这个方法的时候,pthon会自动调用下面的__repr__方法 你可以自己去掉这个方法试试 """ s = str(self.x) + ', ' + str(self.y) return s def __repr__(self): s = "point(" + str(self.x) + ', ' + str(self.y) + ')' return s def __neg__(self): """ 取负值 """ newx = -self.x newy = -self.y return point(newx, newy) def __trunc__(self): """ 去掉小数部分,只取整数部分,注意要和向上取整和向下取整区别开来。 """ newx = self.x.__trunc__() newy = self.y.__trunc__() return point(newx, newy)
运行下面代码
pt1 = point(3,4) pt2 = -pt1 print(pt2)
结果是
-3, -4
再运行下面代码
import math pt3 = point(5.3, 5.6) print(math.trunc(pt3))
结果是
5, 5
和其他类型的算术方法一样,一元算术方法还有一元正号__pos__, 绝对值__abs__, 按位求反__invert__, 将值转换为布尔值__bool__, 按照精度舍入__round__, 向下舍入__floor__, 向上舍入__ceil__。这些方法分别对应于当用这些函数或者符号的时候会被采用:+, abs(), ~, bool(), round(), math.floor(), math.ceil()。
反向方法
所谓反向方法就是当正向方法如__add__(), __mul__()等未在左边对象中被定义或者这些方法在左侧对象被调用并在运行过程中出现NotImplemented的时候(类似于报错,但不是报错)才会被调用,是用来“补救”调用左侧对象中的方法过程中出现了障碍后的一种备选方案。
这里我依然举例子。
class point: def __init__(self, x, y,z): self.x = x self.y = y self.z = z def __str__(self): s = str(self.x)+", " + str(self.y)+", " +str(self.z) return s def __repr__(self): s = "point(" + str(self.x) + "," + str(self.y)+ "," + str(self.z) + ")" return s def __add__(self, other): if type(other) == point: new_x = self.x + other.x new_y = self.y + other.y new_z = self.z + other.z else: new_x = self.x + other new_y = self.y + other new_z = self.z + other return new_x, new_y, new_z def __radd__(self, other): new_x = self.x + other new_y = self.y + other new_z = self.z + other return new_x, new_y, new_z def __sub__(self,other): if type(other) == point: new_x = self.x - other.x new_y = self.y - other.y new_z = self.z + other.z else: new_x = self.x - other new_y = self.y - other new_z = self.z - other return new_x, new_y, new_z def __rsub__(self,other): new_x = other - self.x new_y = other - self.y new_z = other - self.z return new_x, new_y, new_z def __mul__(self, other): if isinstance(other, point): new_x = self.x * other.x new_y = self.y * other.y new_z = self.z * other.z return point(new_x,new_y,new_z) elif isinstance(other, (int, float)): new_x = self.x * other new_y = self.y * other new_z = self.z * other return point(new_x,new_y,new_z) else: return NotImplemented def __rmul__(self, other): if isinstance(other, (int, float)): new_x = self.x * other new_y = self.y * other new_z = self.z * other return point(new_x, new_y, new_z) else: return NotImplemented
这里我先介绍一下当运行3 + point(1,2,3) 语句时候,是怎么运行的。首先python会实例化point这个类自不必说,然后python会自动调用+左边的对象3中的__add__方法,python会发现其整数不能与point进行相加运算的。然后python就去调用右侧对象中的__radd__方法,这样就能够如常进行了。上述分析对于__rmul__, __rsub__等这样的方法是一样的。例如对于3 * point(1,2,3)这样的情况。
好了运行上面的 3 + point(1,2,3) 语句结果如下。
(4, 5, 6)
运行上面的 3 * point(1,2,3) 结果如下。
point(3,6,9)
就地运算符
这份方法很简单,我快速介绍一下。
__iadd__(self, other) 遇到 += 符号时候被调用
__isub__(self, other) 遇到 -= 符号时候被调用
__imul__(self, other) 遇到 *= 符号时候被调用
__idiv__(self, other) 遇到 /= 符号时候被调用
__igrounddiv__(self, other) 遇到 //= 符号时候被调用
__imod__(self, other) 遇到 %= 运算符时候被调用
__ilshift__(self, other) 遇到 <<= 运算符时候被调用
__irshift__(self, other) 遇到 >>= 运算符时候被调用
__iand__(self, other) 遇到 &= 运算符时候被调用
__ior__(self, other) 遇到 |= 运算符时候被调用
__ixor__(self, other) 遇到 ^= 运算符时候被调用
__ipow__(self, other, [, modulo]) 遇到 ** 运算符时候被调用
详细说明__iadd__()方法是如何被定义的如下
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self
def __imul__(self, other):
self.x *= other
self.y *= other
return self
没错,你看到的就是这些方法会返回实例对象self本身。
好了,其他种类的魔术方法还有
转换方法
集合类方法
__iter__和__next__方法
这里不再详述,遇到了,今后再自行百度等。相信要是熟悉了上面的一些魔术方法,其他的魔术方法应该很快能够从总体上去把握和理解。