返回介绍

10.2 Vector 类第1版:与 Vector2d 类兼容

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

Vector 类的第 1 版要尽量与前一章定义的 Vector2d 类兼容。

然而我们会故意不让 Vector 的构造方法与 Vector2d 的构造方法兼容。为了编写 Vector(3, 4) 和 Vector(3, 4, 5) 这样的代码,我们可以让 __init__ 方法接受任意个参数(通过 *args);但是,序列类型的构造方法最好接受可迭代的对象为参数,因为所有内置的序列类型都是这样做的。示例 10-1 展示了 Vector 类的几种实例化方式。

示例 10-1 测试 Vector.__init__ 和 Vector.__repr__ 方法

>>> Vector([3.1, 4.2])
Vector([3.1, 4.2])
>>> Vector((3, 4, 5))
Vector([3.0, 4.0, 5.0])
>>> Vector(range(10))
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])

除了新构造方法的签名外,我还确保了传入两个分量(如 Vector([3, 4]))时,Vector2d 类(如 Vector2d(3, 4))的每个测试都能通过,而且得到相同的结果。

如果 Vector 实例的分量超过 6 个,repr() 生成的字符串就会使用 ... 省略一部分,如示例 10-1 中的最后一行所示。包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的(因此不想让大型对象在控制台或日志中输出几千行内容)。使用 reprlib 模块可以生成长度有限的表示形式,如示例 10-2 所示。

在 Python 2 中,reprlib 模块的名字是 repr。2to3 工具能自动重写 repr 导入的内容。

示例 10-2 是第 1 版 Vector 类的实现代码(以示例 9-2 和示例 9-3 中的代码为基础)。

示例 10-2 vector_v1.py:从 vector2d_v1.py 衍生而来

from array import array
import reprlib
import math


class Vector:
  typecode = 'd'

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

  def __iter__(self):
    return iter(self._components)  ➋

  def __repr__(self):
    components = reprlib.repr(self._components)  ➌
    components = components[components.find('['):-1]  ➍
    return 'Vector({})'.format(components)

  def __str__(self):
    return str(tuple(self))

  def __bytes__(self):
    return (bytes([ord(self.typecode)]) +
        bytes(self._components))  ➎

  def __eq__(self, other):
    return tuple(self) == tuple(other)

  def __abs__(self):
    return math.sqrt(sum(x * x for x in self))  ➏

  def __bool__(self):
    return bool(abs(self))

  @classmethod
  def frombytes(cls, octets):
    typecode = chr(octets[0])
    memv = memoryview(octets[1:]).cast(typecode)
    return cls(memv)  ➐

❶ self._components 是“受保护的”实例属性,把 Vector 的分量保存在一个数组中。

❷ 为了迭代,我们使用 self._components 构建一个迭代器。1

1iter() 函数和 __iter__ 方法在第 14 章讨论。

❸ 使用 reprlib.repr() 函数获取 self._components 的有限长度表示形式(如 array('d', [0.0, 1.0, 2.0, 3.0, 4.0, ...]))。

❹ 把字符串插入 Vector 的构造方法调用之前,去掉前面的 array('d' 和后面的 )。

❺ 直接使用 self._components 构建 bytes 对象。

❻ 不能使用 hypot 方法了,因此我们先计算各分量的平方之和,然后再使用 sqrt 方法开平方。

❼ 我们只需在 Vector2d.frombytes 方法的基础上改动最后一行:直接把 memoryview 传给构造方法,不用像前面那样使用 * 拆包。

我使用 reprlib.repr 的方式需要做些说明。这个函数用于生成大型结构或递归结构的安全表示形式,它会限制输出字符串的长度,用 '...' 表示截断的部分。我希望 Vector 实例的表示形式是 Vector([3.0, 4.0, 5.0]) 这样,而不是 Vector(array('d', [3.0, 4.0, 5.0])),因为 Vector 实例中的数组是实现细节。因为这两种构造方法的调用方式所构建的 Vector 对象是一样的,所以我选择使用更简单的句法,即传入列表参数。

编写 __repr__ 方法时,本可以使用这个表达式生成简化的 components 显示形式:reprlib.repr(list(self._components))。然而,这么做有点浪费,因为要把 self._components 中的每个元素复制到一个列表中,然后使用列表的表示形式。我没有这么做,而是直接把 self._components 传给 reprlib.repr 函数,然后去掉 [] 外面的字符,如示例 10-2 中 __repr__ 方法的第二行所示。

 调用 repr() 函数的目的是调试,因此绝对不能抛出异常。如果 __repr__ 方法的实现有问题,那么必须处理,尽量输出有用的内容,让用户能够识别目标对象。

注意,__str__、__eq__ 和 __bool__ 方法与 Vector2d 类中的一样,而 frombytes 方法也只变了一个字符(最后一行把 * 去掉了)。这是 Vector2d 可迭代的好处之一。

顺便说一下,我们本可以让 Vector 继承 Vector2d,但是我没这么做,原因有二。其一,两个构造方法不兼容,因此不建议继承。这一点可以通过适当处理 __init__ 方法的参数解决,不过第二个原因更重要:我想把 Vector 类当作单独的示例,以此实现序列协议。接下来,我们先讨论协议这个术语,然后实现序列协议。

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

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

发布评论

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