首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

自定义 Python 类中的运算符和函数重载(下)

重载内置运算符

改变运算符的行为与改变函数的行为一样简单。在类中定义其相应的特殊方法,运算符就会根据这些方法中定义的行为进行工作。

这些不同于上述特殊方法的意义是,除了self外他们需要接受另一参数,一般用other来指代。让我们看看几个例子。

使对象能够使用 + 进行加操作

与运算符 + 对应的特殊方法是__add__()方法。添加自定义__add__()会改变运算符的行为。建议__add__()返回类的新实例,而不是修改调用实例本身。在 Python 中这种行为很常见:

上面可以看到,对str对象使用运算符 + 实际上返回一个新str实例,从而保持调用实例 a 的值不被修改。要更改它, 我们需要显式地将新实例赋给a。

让我们实现在Order类中使用运算符向我们的购物车追加新项目的能力。我们将遵循建议的做法,并使运算符返回一个新的Order实例,它有我们所需的更改,而不是直接对我们的实例进行更改:

同样, 还有__sub__(),__mul__()等其他重定义-,*的特殊方法。这些方法也应返回类的新实例。

缩写:+ = 运算符

运算符+=是表达式obj1 = obj1 + obj2的缩写。与之对应的特殊方法是__iadd__()。__iadd__()方法应直接对self参数进行更改,并返回结果 (可能也可能不是self)。此行为与后者创建新对象并返回的方式__add__()完全不同,正如你在上面所看到的那样。

大致而言, 对两个对象使用+=都等同于:

这里,result是__iadd__()返回的值。第二次分配由 Python 自动处理,这意味着你不需要显式分配obj1到结果,就像在obj1 = obj1 + obj2中的情况一样。

让我们在Order类中实现,以便新的项目可以追加到购物车使用:

可以看到, 任何更改都是直接对self进行的,然后返回。返回一些随机值时会发生什么情况,如字符串或整数?

尽管相关项目被追加到购物车中,但order的值更改为了__iadd__()所返回的值。Python 隐式地处理了分配的任务。如果忘记在实现中返回某些内容,这可能会导致令人惊讶的结果:

由于所有 Python 函数 (或方法) 都是隐式返回None的,因此order被重新分配了None,REPL 会话在order检查时不会显示任何输出。看order的类型,现在是NoneType。因此,请始终确保你在__iadd__()实现中返回一些内容,并且它是运算的结果,而不是其他任何内容。

类似于__iadd__(),你对定义-=,*=,/=有__isub__(),__imul__(),__idiv__()等其他特殊的方法。

注:当你的类定义中缺失__iadd__()等函数,但你仍对对象使用它们的运算符,Python 使用__add__()等函数使用其运算符来获取操作的结果并将其分配给调用实例。一般而言,只要在你的类中__add__()等函数正常工作 (返回某种操作的结果),不实现__iadd__()等函数就是安全的。

Python文档对这些方法有很好的解释。另外,请看一下这个示例,它显示了使用+=等不可变类型时所涉及的警告和其他操作。

使用 [] 对对象进行索引和切片

运算符[]称为索引运算符,用于 Python 中的各种情境文中,例如在序列中的索引处获取值、获取与字典中的键值关联的值或通过切片获取序列的一部分。可以使用特殊方法__getitem__()更改其行为。

让我们配置我们的Order类,以便我们可以直接使用该对象并从购物车中获取项目:

你会注意到__getitem__()的参数名称不是index而是key。这是因为该参数可以主要为三种形式:一个整数值,在这种情况下,它是一个索引或字典键值;一个字符串值,在这种情况下,它是一个字典键值;一个切片对象,在这种情况下,它将切片该类使用的序列。虽然还有其他可能性,但这些都是最常见的。

由于我们的内部数据结构是一个列表,我们可以使用运算符[]切片列表,就像在这种情况下,参数key将是切片对象。这是在你的类中定义__getitem__()的最大优点之一。只要你使用支持切片的数据结构 (列表、元组、字符串等),就可以将对象配置为直接切片结构:

注:有一个类似的特殊方法__setitem__(),用于定义obj[x] = y的行为。此方法除了self外接受两个参数 (通常称为key和value) ,还可用于将key对应值更改为value。

反向运算符: 使类在数学上正确

在定义__add__(),__sub__(),__mul__()等类似的特殊方法时, 当类实例是左侧操作数时,可以使用运算符,如果类实例是右侧操作数,则运算符将无法工作:

如果你的类表示像向量、坐标或复数等数学实体,则应用运算符应在两种情况下都正常工作,因为它是有效的数学运算。

此外,如果运算符只在实例为左操作数时起作用,则在许多情况下,我们违反了交换律的基本原理。因此,为了帮助你使类在数学上正确,Python 提供了反向特殊方法,例如__radd__(),__rsub__(),__rmul__()等等。

这些句柄调用 (如x + obj,x - obj和x * obj),其中x不是相关类的实例。就像__add__()等函数一样,这些反向特殊方法应返回一个修改后的新的类实例,而不是修改调用实例本身。

让我们在Order类中配置__radd__(),使其实现在购物车的前面追加一些东西的功能。当购物车按订单的优先级组织时,可以使用此方法:

一个完整的示例

要归纳所有的这些点,最好看看一个实现这些运算符的示例类。

让我们从实现我们自己的类CustomComplex来表示复数开始。我们的类对象将支持各种内置函数和运算符,使它们的行为与内置的复数类非常相似:

构造函数只处理一种调用CustomComplex(a, b)。它采用位置参数,表示复数的实部和虚部。

让我们在类内定义两种函数conjugate()和argz(),并分别给出复数共轭和复数的参数:

注:__class__不是特殊方法,而是默认情况下存在的类属性。它有一个对类的引用。通过在这里使用,我们得到了它,然后以通常的方式调用构造函数。换言之,这等同于CustomComplex(real, imag)。这样做是为了避免如果某天类的名称发生更改所要导致的重构代码。

接下来我们配置abs()来返回复数的模量:

我们将按照建议的__repr__()和__str__()的区别,为解析字符串表示形式使用前者,为了更"漂亮" 的表示后者。

__repr__()方法将简单地返回一个CustomComplex(a, b)字符串,以便我们可以调用eval()重新创建对象,而__str__()方法将返回括号中的复数,如(a+bj):

在数学上, 可以添加任意两个复数或将实数添加到复数中。让我们用这样的方式配置运算符+,这样它就可以在这两种情况下工作。

该方法将检查右侧运算符的类型。如果它是int或float,它将只增加实部 (因为任何实数a等价于a+0j),而在是另一个复数的情况下,它的两个部分都会更改:

同样,我们定义-和*的行为:

由于加法和乘法都是有交换律的,我们可以通过在__radd__()和__rmul__()中分别调用__add__()和__mul__()来定义它们的反向算子。另一方面,由于减法是不可交换的,__rsub__()的行为需要被定义:

注:你可能已经注意到, 我们没有添加构造来处理CustomComplex实例。这是因为,在这种情况下,两个操作数都是我们类的实例,__rsub__()不负责处理操作。相反,__sub__()将被调用。这是一个微妙但重要的细节。

现在,我们来看看这两个运算符,==和!=。用于它们的特殊方法分别是__eq__()和__ne__()。如果两个复数的实部和虚部分别相等,则它们是相等的。若其中任一不等,则两个复数不等:

注:浮点数参考(http://floating-point-gui.de/errors/comparison/)是一篇讨论了如何比较浮点精度和浮点数的文章。它强调了直接比较浮点的注意事项,这正是我们在这里做的事情。

还可以使用简单的公式得到复数的幂。我们使用特殊方法__pow__()为内置的pow()和运算符**配置行为:

注:仔细查看方法的定义。我们调用abs()是为了得到复数的模量。因此,一旦定义了类中特定函数或运算符的特殊方法,就可以在同一类的其他方法中使用它。

让我们创建这个类的两个实例,一个具有正虚部,一个具有负虚部:

字符串表示形式:

使用带repr()的eval()重新创建对象:

加法、减法和乘法:

相等不等检查:

最后得到复数的幂:

正如你所看到的, 我们的自定义类的对象的行为和外观类似于内置类,并且非常 Pythonic。此类的完整示例代码如下:

回顾和资源

在本教程中,你了解了 Python 数据模型以及数据模型如何用于生成 Pythonic 的类。你了解了如何更改内置函数 (如len()、abs()、str()、bool()等等) 的行为。你还了解了如何更改内置运算符的行为(如+、-、*、**等等)。

读完本文后,你可以很好地创建类,利用 Python 的最佳惯用功能,使你的对象 Pythonic!

有关数据模型以及函数和运算符重载的更多信息,请查看以下资源:

3.3 节,Python 文档中数据模型部分的特殊方法名称(https://docs.python.org/3/reference/datamodel.html#special-method-names )

Ramalho的《流畅 Python 》(https://www.amazon.cn/dp/1491946008/?tag=n )

Python 技巧(https://realpython.com/products/python-tricks-book/ )

英文原文:https://realpython.com/operator-function-overloading/

译者:β

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180619A084D900?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券