61

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

 6 years ago
source link: http://www.10tiao.com/html/384/201806/2651305774/1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

点击上方“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!


  • 译者:β

  • https://realpython.com/operator-function-overloading/

  • Python开发整理发布,转载请联系作者获得授权

【点击成为Java大神】


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK