使用 singledispatch 在 Python 中追溯添加方法

了解更多关于在我们涵盖七个 PyPI 库的系列中解决常见 Python 问题的信息。
172 位读者喜欢这个。
Why and how to handle exceptions in Python Flask

图片来自 Unsplash.com,知识共享零许可 

Python 是当今使用最流行的编程语言之一,这有充分的理由:它是开源的,用途广泛(例如 Web 编程、商业应用程序、游戏、科学编程等等),并且它有一个充满活力和专注的社区为其提供支持。这个社区是我们拥有如此庞大、多样化的软件包范围的原因,这些软件包可在 Python 包索引 (PyPI) 中获得,以扩展和改进 Python 并解决不可避免出现的故障。

在本系列中,我们将研究七个 PyPI 库,它们可以帮助您解决常见的 Python 问题。今天,我们将研究 singledispatch,这是一个允许您追溯地向 Python 库添加方法的库。

singledispatch

想象一下,您有一个“形状”库,其中包含 Circle 类、Square 类等。

Circle 有一个半径Square 有一个边长,而 Rectangle高度宽度。我们的库已经存在;我们不想更改它。

但是,我们确实想向我们的库添加一个面积计算。如果我们不与其他人共享这个库,我们可以只添加一个 area 方法,这样我们就可以调用 shape.area() 而不必担心形状是什么。

虽然可以深入到类中并添加一个方法,但这并不是一个好主意:没有人期望他们的类会增长新的方法,并且事情可能会以奇怪的方式崩溃。

相反,functools 中的 singledispatch 函数可以来帮助我们。

@singledispatch
def get_area(shape):
    raise NotImplementedError("cannot calculate area for unknown shape",
                              shape)

get_area 函数的“基本”实现失败。这确保了如果我们得到一个新的形状,我们将干净利落地失败,而不是返回无意义的结果。

@get_area.register(Square)
def _get_area_square(shape):
    return shape.side ** 2
@get_area.register(Circle)
def _get_area_circle(shape):
    return math.pi * (shape.radius ** 2)

以这种方式做事的一个好处是,如果有人编写了一个新的形状,旨在与我们的代码良好配合,他们可以自己实现 get_area

from area_calculator import get_area

@attr.s(auto_attribs=True, frozen=True)
class Ellipse:
    horizontal_axis: float
    vertical_axis: float

@get_area.register(Ellipse)
def _get_area_ellipse(shape):
    return math.pi * shape.horizontal_axis * shape.vertical_axis

调用 get_area 非常简单。

print(get_area(shape))

这意味着我们可以更改具有长 if isintance()/elif isinstance() 链的函数以这种方式工作,而无需更改接口。下次当您想检查 if isinstance 时,请尝试使用 singledispatch

在本系列的下一篇文章中,我们将研究 tox,这是一个用于自动化 Python 代码测试的工具。

回顾本系列之前的文章

接下来阅读什么
标签
Moshe sitting down, head slightly to the side. His t-shirt has Guardians of the Galaxy silhoutes against a background of sound visualization bars.
自 1998 年以来,Moshe 一直参与 Linux 社区,在 Linux “安装聚会”中提供帮助。自 1999 年以来,他一直在编写 Python 程序,并为核心 Python 解释器做出了贡献。Moshe 自从这些术语出现之前就一直是 DevOps/SRE,他非常关心软件可靠性、构建可重复性以及其他此类事情。

3 条评论

为什么不简单地创建一个 Shape 类,其中包含所有形状通用的方法,然后在 Shape 类中的 get_area 中简单地引发您的 NotImplementedError 异常,然后让您的所有形状都继承自 Shape,然后在子类中根据需要覆盖 get_area?这不就是继承的目的吗?

在您的示例中,get_area 没有副作用,因此增长新方法不是问题。我不明白 singledispatch 除了使您的代码更复杂、更难阅读以及许多人会说不符合 Python 风格之外,还能完成什么。

我也倾向于同意您的观点。我认为更符合 Python 风格的方法是使用 dict 将类型映射到平方函数/lambda。这可能是对此的包装器,但它不明确,并且是另一个需要维护的库依赖项。

回复 作者:Jason L Gray (未验证)

太棒了

Creative Commons License本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.