专栏名称: VPointer
软件开发
目录
相关文章推荐
风动幡动还是心动  ·  卷土重来 ·  昨天  
风动幡动还是心动  ·  卷土重来 ·  昨天  
望京博格投基  ·  美股估值到底如何?看数据~ ·  昨天  
望京博格投基  ·  美股估值到底如何?看数据~ ·  昨天  
51好读  ›  专栏  ›  VPointer

Python学习之路32-运算符重载

VPointer  · 掘金  ·  · 2018-07-22 11:32

正文

阅读 5

Python学习之路32-运算符重载

《流畅的Python》笔记。

本篇是“面向对象惯用方法”的第六篇,也是最后一篇。本篇将讨论Python中的运算符重载。

1. 前言

Python中的运算符重载和C++中的运算符重载并不一样,C++中同一运算符可以有多个重载函数,Python中的运算符重载其实是实现运算符的同名特殊方法。

本篇只讨论一元运算符和中缀运算符,内容如下:

  • Python如何处理中缀运算符中不同类型的操作数;
  • 使用鸭子类型或白鹅类型处理不同类型的操作数;
  • 中缀运算符如何表明自己无法处理操作数;
  • 众多比较运算符的特殊行为;
  • 增量运算符的默认处理方式和重载方式。

不过,需要说明的是,并不是所有的运算符都能重载:

  • 不能重载内置类型的运算符;
  • 不能新建运算符,只能重载现有的;
  • is and or not 不能重载。

本文中的示例延用 《Python学习之路29》 中的多维向量 Vector

2. 一元运算符

本节主要介绍4个一元运算符,它们分别是:

  • - ( __neg__ ):一元取负运算符,如 x = 2 ,则 -x == 2
  • + ( __pos__ ):一元取正运算符,通常是 x == +x ,但也有特例;
  • ~ ( __invert__ ):对整数按位取反,定义为 ~x == -(x + 1)
  • abs() 函数:Python语言参考手册把它也列为了一元运算符,它对应的就是之前多次用到的 __abs__

在实现过程中需要遵循 这些 运算符的一个基本规则: 始终返回一个新对象 !也就是说不能修改 self ,要创建并返回合适类型的实例。以下补充两个 Vector 类的运算符重载:

def __neg__(self):
    return Vector(-x for x in self)

def __pos__(self):
    return Vector(self)
复制代码

x +x 何时不等 ?以下是两个例子:

  • 如果 decimal.Decimal 所在上下文的精度不同,则有可能不等,如下:

    >>> import decimal
    >>> ctx = decimal.getcontext()
    >>> ctx.prec = 40
    >>> one_third = decimal.Decimal("1") / decimal.Decimal("3")
    >>> one_third
    Decimal('0.3333333333333333333333333333333333333333')
    >>> one_third == +one_third
    True
    >>> ctx.prec = 28    # 这是默认精度
    >>> one_third == +one_third
    False
    >>> +one_third
    Decimal('0.3333333333333333333333333333')
    复制代码
  • collections.Counter 在相加时,负值和零值计数会从结果中剔除,而一元运算符 + 对它来说等同于加上一个空 Counter ,如下:

    >>> ct = Counter("abracadabra")
    >>> ct["r"] = -3
    >>> ct["d"] = 0
    >>> ct
    Counter({'a': 5, 'b': 2, 'c': 1, 'd': 0, 'r': -3})
    >>> +ct   # 与ct不等
    Counter({'a': 5, 'b': 2, 'c': 1})
    复制代码

3. 重载向量加法运算符+

目前版本的 Vector 不支持向量相加,因为没有重载 + 运算符。我们的要求如下:

  • 它能实现两个 Vector 相加,并且两个长度不等的 Vector 也能相加,短的那个用 0.0 填充;
  • 能与任何可迭代对象相加,但当这个可迭代对象中的元素不能与浮点数做加法运算时,则抛出 NotImplemented 异常;
def __add__(self, other):
    try:
        pairs = itertools.zip_longest(self, other, fillvalue=0.0)  # 自动填充
        return Vector(a + b for a, b in pairs)
    except TypeError:
        # 它不是一个异常类,而是一个单例值!所以用的是return,而不是raise
        return NotImplemented   

def __radd__(self, other):   # 实现反向相加
    return self + other

# 在控制台中运行的示例,省略了import语句
>>> v1 = Vector([1, 2, 3])
>>> v1 + Vector([2, 3, 4])  # 可以和同类型的相加
Vector([3.0, 5.0, 7.0])
>>> v1 + (1, 2, 3)   # 和其他可迭代对象也能相加
Vector([2.0, 4.0, 6.0])
>>> v1 + (1, 2)    # 长度不同也能相加
Vector([2.0, 4.0, 3.0])
>>> v1 + Vector2d(1, 2)   # 由于我们之前实现的Vector2d也是可迭代对象,所以也能和Vector相加
Vector([2.0, 4.0, 3.0])
>>> (1, 2, 3) + v1   # <1> 反向也能相加,见解释
Vector([2.0, 4.0, 6.0])
复制代码

解释

  • __radd__ __rsub__ 这种前面带 r 的方法一般被称作“反向”运算方法或“右向”运算方法,如果没有实现这种方法,上述代码 <1> 处的语句就会抛出 TypeError

  • 对于表达式 a + b 来说,解释器会执行如下几步:

    • 如果 a __add__ 方法,调用 a.__add__(b)
    • 如果 a.__add__(b) 返回 NotImplemented ,或者 a 没有 __add__ 方法,则检查 b 有没有 __radd__ 方法,如果有,则调用 b.__radd__(a)
    • 如果 b.__radd__(a) 返回 NotImplemented ,或者 b 没有 __radd__ 方法,则抛出 TypeError ,并在错误消息中指明 操作数类型不支持

    其他有反向运算方法的运算符在调用时也是上面这个逻辑。

  • __radd__ 等反向运算的实现通常就如上述代码这么简单暴力:直接委托给正向运算。

  • 在实现 __add__ 时,我们并没有去判断 other 的类型或者它的元素的类型,而是捕获 TypeError 异常。这是在给 other 调用反向运算方法的一个机会。如果调用成功, other 就能被当做另一个操作数的“同类”,这也遵循了鸭子类型精神。

4. 重载乘法运算符

4.1 重载数乘运算*

这里实现的是向量的数乘运算,我们希望 任何实数 都能和 Vector 做数乘预算(也叫做元素级乘法, elementwise multiplication),添加的两个方法如下:

def __mul__(self, scalar):
    if isinstance(scalar, numbers.Real):  
        return Vector(n * scalar for n in self)
    else:
        return NotImplemented

def __rmul__(self, scalar):
    return self * scalar

# 以下是在控制台中运行的示例
>>> v1 = Vector([1,2,3])
>>> 2 * v1
Vector([2.0, 4.0, 6.0])
>>> v1 * True    # bool是int的子类
Vector([1.0, 2.0, 3.0])
>>> from fractions import Fraction
>>> v1 * Fraction(1






请到「今天看啥」查看全文


推荐文章
风动幡动还是心动  ·  卷土重来
昨天
风动幡动还是心动  ·  卷土重来
昨天
望京博格投基  ·  美股估值到底如何?看数据~
昨天
望京博格投基  ·  美股估值到底如何?看数据~
昨天
吃喝玩乐新分类  ·  招聘、求职信息 | 05.22
7 年前
体坛老司机  ·  姚明吸毒被抓?公开亮相证明清白
7 年前
喂喂打工  ·  2017年钢厂产能置换项目汇总
7 年前