python进阶学习(二)重载运算符

C/C++中的重载运算符机制十分的好用,java虽然不能直接重载运算符,但是可以通过实现comparator接口等方式实现类之间的比较,那么python又是如何重载运算符的呢?


前言

重载运算符如果使用得当,那么API会变得更加好用,而且代码会变得更加容易阅读,但是一旦被滥用(比如把加法运算符重载用作减法),那么就会让阅读代码的人感到困惑不解了,另外还可能导致缺陷和意料之外的性能瓶颈。

python对运算符重载进行了一定的限制,很多限制其实和C++是一样的:

  • 不能重载内置类型的运算符
  • 不能新建运算符,只能重载现有的
  • python中某些运算符不能重载,例如is、and、or还有not

实例

python可以通过重载类中内置的魔法方法来实现运算符的重载,下面通过例子来说明运算符重载的方法:

import math
# 定义坐标上的Point类,拥有x坐标和y坐标这两个属性
class Point:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "(%s,%s)" % (self.x, self.y)
    
    # 重载绝对值方法,表示含义为点到原点的距离
    def __abs__(self):
        return math.sqrt(self.x**2+self.y**2)
    
    # 重载取负运算符
    def __neg__(self):
        return Point(-self.x, -self.y)
    
    # 重载取正运算符
    def __pos__(self):
        return Point(self.x, self.y)
        
    # 重载二元运算符,加法
    def __add__(self, other):
        if not isinstance(other, Point):
            raise Exception("传入对象不是Point对象的实例")
        return Point(self.x+other.x, self.y+other.y)
        
    # 重载二元运算符,减法
    def __sub__(self, other):
        if not isinstance(other, Point):
            raise TypeError("传入对象不是Point对象的实例")
        return Point(self.x-other.x, self.y-other.y)
    
    # 重载比较运算符
    def __eq__(self, other):
        if not isinstance(other, Point):
            raise TypeError("传入对象不是Point对象的实例")
        return self.x == other.x and self.y == other.y
    
    def __gt__(self, other):
        if not isinstance(other, Point):
            raise TypeError("传入对象不是Point对象的实例")
        return abs(self) > abs(other)

if __name__ == '__main__':
    p1 = Point(-1, 2)
    print("取绝对值:",abs(p1))
    print("取负:",-p1)
    p2 = Point(2,4)
    print("加法:",p1+p2)
    print("减法:",p1-p2)
    print("比较大小:", p1>p2)

上面的结果为:

取绝对值: 2.23606797749979
取负: (1,-2)
加法: (1,6)
减法: (-3,-2)
比较大小: False

可以看到,python每次重载一个运算符,都要重载一个特殊方法,对于比较运算符来说,有__lt__,__le__,__eq__,__ne__,__gt__,__ge__,这么六个,如果都有需要用到这些运算符,自己一一实现是很麻烦的,我们可以使用标准库functools中的total_ordering装饰器来简化重载比较运算符。

使用total_ordering很简单,只要在定义的class类上添加上装饰器@total_ordering,并且在类中定义__eq__()方法和__lt__(),__le__(),__gt__(),__ge__()中的一个,装饰器会帮助我们自动实现其余的,下面来看看例子:

import math
from functools import total_ordering
# 定义坐标上的Point类,拥有x坐标和y坐标这两个属性
@total_ordering
class Point:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "(%s,%s)" % (self.x, self.y)
    
    # 重载绝对值方法,表示含义为点到原点的距离
    def __abs__(self):
        return math.sqrt(self.x**2+self.y**2)
    
    # 重载比较运算符,只要到原点的距离相等,就认为两个类相等
    def __eq__(self, other):
        if not isinstance(other, Point):
            raise TypeError("传入对象不是Point对象的实例")
        return abs(self) == abs(other)
    
    def __gt__(self, other):
        if not isinstance(other, Point):
            raise TypeError("传入对象不是Point对象的实例")
        return abs(self) > abs(other)

if __name__ == '__main__':
    p1 = Point(-1, 2)
    p2 = Point(2,4)
    print("比较大小:", p1

因为我们实现和等于操作和大于操作,total_ordering就可以通过这两个来实现剩下的比较运算符,例如,小于等于就可以解释为not self > other or self == other,这样就可以实现所有的比较操作。

所有的运算符相关的方法都可以在官方文档中找到,这里罗列部分:

一元运算符 __neg__-,__pos__+,__abs__abs()
比较运算符 __lt__<,__le__<=,__eq__==,__ne__!=,__gt__>,__ge__>=
算术运算符

__add__+,__sub__-,__mul__*,__mod__%,__truediv__/,__floordiv__//,__pow__**,__round__round()

反向算术运算符 __radd__,__rsub__,__rmul__,__rmod__,__rtruediv__,__rfloordiv__,__rpow__
增量赋值算术运算符 __iadd__,__isub__,__imul__,__imod__,__itruediv__,__ifloordiv__,__ipow__
位运算符 __invert__~,__lshift__<<,__rshift__>>,__and__&,__or__|,__xor__^
反向位运算符 __rlshift__,__rrshift__,__rand__,__ror__,__rxor__
增量赋值位运算符

__ishift__,__irshift__,__iand__,__ior__,__ixor__

 

 

你可能感兴趣的:(python,python)