返回介绍

13.4 重载标量乘法运算符 *

发布于 2024-02-05 21:59:47 字数 7157 浏览 0 评论 0 收藏 0

Vector([1, 2, 3]) * x 是什么意思?如果 x 是数字,就是计算标量积(scalar product),结果是一个新 Vector 实例,各个分量都会乘以 x——这也叫元素级乘法(elementwise multiplication)。

  >>> v1 = Vector([1, 2, 3])
  >>> v1 * 10
  Vector([10.0, 20.0, 30.0])
  >>> 11 * v1
  Vector([11.0, 22.0, 33.0])

涉及 Vector 操作数的积还有一种,叫两个向量的点积(dot product);如果把一个向量看作 1×N 矩阵,把另一个向量看作 N×1 矩阵,那么就是矩阵乘法。NumPy 等库目前的做法是,不重载这两种意义的 *,只用 * 计算标量积。例如,在 NumPy 中,点积使用 numpy.dot() 函数计算。5

5从 Python 3.5 起,@ 记号可以用作中缀点积运算符。详情参见“Python 3.5 新引入的中缀运算符 @”附注栏。

回到标量积的话题。我们依然先实现最简可用的 __mul__ 和 __rmul__ 方法:

  # 在Vector类中定义

  def __mul__(self, scalar):
    return Vector(n * scalar for n in self)

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

这两个方法确实可用,但是提供不兼容的操作数时会出问题。scalar 参数的值要是数字,与浮点数相乘得到的积是另一个浮点数(因为 Vector 类在内部使用浮点数数组)。因此,不能使用复数,但可以是 int、bool(int 的子类),甚至 fractions.Fraction 实例等标量。

我们可以像示例 13-10 那样,采用鸭子类型技术,在 __mul__ 方法中捕获 TypeError。但是,这个问题有个更易于理解的方式,而且也更合理:白鹅类型。我们将使用 isinstance() 检查 scalar 的类型,但是不硬编码具体的类型,而是检查 numbers.Real 抽象基类。这个抽象基类涵盖了我们所需的全部类型,而且还支持以后声明为 numbers.Real 抽象基类的真实子类或虚拟子类的数值类型。示例 13-11 展示了白鹅类型的实际运用——显式检查抽象类型。完整的代码清单参见本书的代码仓库。

 你可能还记得 11.6 节说过,decimal.Decimal 没有把自己注册为 numbers.Real 的虚拟子类。因此,Vector 类不会处理 decimal.Decimal 数字。

示例 13-11 vector_v7.py:增加 * 运算符方法

from array import array
import reprlib
import math
import functools
import operator
import itertools
import numbers  # ➊

class Vector:
  typecode = 'd'

  def __init__(self, components):
    self._components = array(self.typecode, components)

  # 排版需要,省略了很多方法
  # 参见https://github.com/fluentpython/example-code中的vector_v7.py

  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  # ➍

❶ 为了检查类型,导入 numbers 模块。

❷ 如果 scalar 是 numbers.Real 某个子类的实例,用分量的乘积创建一个新 Vector 实例。

❸ 否则,返回 NotImplemented,让 Python 尝试在 scalar 操作数上调用 __rmul__ 方法。

❹ 这里,__rmul__ 方法只需执行 self * scalar,委托给 __mul__ 方法。

有了示例 13-11 中的代码之后,我们可以拿 Vector 实例乘以常规的标量值和不那么寻常的数字类型了:

  >>> v1 = Vector([1.0, 2.0, 3.0])
  >>> 14 * v1
  Vector([14.0, 28.0, 42.0])
  >>> v1 * True
  Vector([1.0, 2.0, 3.0])
  >>> from fractions import Fraction
  >>> v1 * Fraction(1, 3)
  Vector([0.3333333333333333, 0.6666666666666666, 1.0])

通过实现 + 和 *,我们讲解了编写中缀运算符最常用的模式。+ 和 * 用的技术对表 13-1 中列出的所有运算符都适用(就地运算符在 13.6 节讨论)。

表13-1:中缀运算符方法的名称(就地运算符用于增量赋值;比较运算符在表13-2中)

运算符

正向方法

反向方法

就地方法

说明

+

__add__

__radd__

__iadd__

加法或拼接

-

__sub__

__rsub__

__isub__

减法

*

__mul__

__rmul__

__imul__

乘法或重复复制

/

__truediv__

__rtruediv__

__itruediv__

除法

//

__floordiv__

__rfloordiv__

__ifloordiv__

整除

%

__mod__

__rmod__

__imod__

取模

divmod()

__divmod__

__rdivmod__

__idivmod__

返回由整除的商和模数组成的元组

**,pow()

__pow__

__rpow__

__ipow__

取幂*

@

__matmul__

__rmatmul__

__imatmul__

矩阵乘法#

&

__and__

__rand__

__iand__

位与

|

__or__

__ror__

__ior__

位或

^

__xor__

__rxor__

__ixor__

位异或

<<

__lshift__

__rlshift__

__ilshift__

按位左移

>>

__rshift__

__rrshift__

__irshift__

按位右移

* pow 的第三个参数 modulo 是可选的:pow(a, b, modulo),直接调用特殊方法时也支持这个参数(如 a.__pow__(b, modulo))。

# Python 3.5 新引入的。

众多比较运算符也是一类中缀运算符,但是规则稍有不同。我们将在下一节讨论众多比较运算符。

下述附注栏介绍了 Python 3.5(写作本书时尚未发布 6)引入的 @ 运算符,选读。

6现已发布。——编者注

Python 3.5 新引入的中缀运算符 @

Python 3.4 没有为点积提供中缀运算符。不过,写作本书时,Python 3.5 的 pre-alpha 版实现了“PEP 465 — A dedicated infix operator for matrix multiplication”,提供了点积所需的 @ 记号(例如,a @ b 是 a 和 b 的点积)。@ 运算符由特殊方法 __matmul__、__rmatmul__ 和 __imatmul__ 提供支持,名称取自“matrix multiplication”(矩阵乘法)。目前,标准库还没用到这些方法,但是 Python 3.5 的解释器能识别,因此 NumPy 团队(以及我们自己)可以在用户定义的类型中支持 @ 运算符。Python 解析器也做了修改,能处理中缀运算符 @(在 Python 3.4 中,a @ b 是一种句法错误)。

为了体验一下,我从源码编译了 Python 3.5,然后为 Vector 实现了点积运算符 @,还做了测试。

下面是我做的最简单的测试:

>>> va = Vector([1, 2, 3])
>>> vz = Vector([5, 6, 7])
>>> va @ vz == 38.0  # 1*5 + 2*6 + 3*7
True
>>> [10, 20, 30] @ vz
380.0
>>> va @ 3
Traceback (most recent call last):
  ...
TypeError: unsupported operand type(s) for @: 'Vector' and 'int'

下面是相应特殊方法的代码:

class Vector:
  # 排版需要,省略了很多方法
    
  def __matmul__(self, other):
    try:
      return sum(a * b for a, b in zip(self, other))
    except TypeError:
      return NotImplemented
    
  def __rmatmul__(self, other):
    return self @ other

完整的源码在本书代码仓库里的 vector_py3_5.py 文件中。

记得要在 Python 3.5 中测试,否则会导致 SyntaxError !

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文