众多比较运算符和增量赋值运算符
众多比较运算符
Python解释器对众多比较运算符(==、!=、>、=、<=)的处理与前文类似,不过在两个方面有重大区别。
• 正向和反向调用使用的是同一系列方法。这方面的规则如表13-2所示。例如,对==来说,正向和反向调用都是__eq__方法,只是把参数对调了;而正向的__gt__方法调用的是反向的__lt__方法,并把参数对调。
• 对==和!=来说,如果反向调用失败,Python会比较对象的ID,而不抛出TypeError。

现在Python 3返回结果是对__eq__结果的取反。对于排序比较运算符,Python 3抛出TypeError,并把错误消息设为’unorderable types: int() < tuple()’。
来分析并改进Vector.__eq__方法的行为,之前是这样定义的
#示例1
class Vector:
# 省略了很多行
def __eq__(self, other):
return (len(self) == len(other) and
all(a == b for a, b in zip(self, other)))
这个方法的行为如示例2
#示例2
>>> va = Vector([1.0, 2.0, 3.0])
>>> vb = Vector(range(1, 4))
>>> va == vb # ➊
True
>>> vc = Vector([1, 2])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> vc == v2d # ➋
True
>>> t3 = (1, 2, 3)
>>> va == t3 # ➌
True
➊ 两个具有相同数值分量的Vector实例是相等的。
➋ 如果Vector实例的分量与Vector2d实例的分量都相等,那么两个实例相等。
➌ Vector实例的分量与元组或其他任何可迭代对象的元素相等,那么对象也相等。
示例示例2中的最后一个结果可能不是很理想
#示例3:改进Vector类的__eq__方法
def __eq__(self, other):
if isinstance(other, Vector): #➊
return (len(self) == len(other) and
all(a == b for a, b in zip(self, other)))
else:
return NotImplemented #➋
➊ 如果other操作数是Vector实例(或者Vector子类的实例),那就像之前那样比较。
➋ 否则,返回NotImplemented。
#示例4
>>> va = Vector([1.0, 2.0, 3.0])
>>> vb = Vector(range(1, 4))
>>> va == vb # ➊
True
>>> vc = Vector([1, 2])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> vc == v2d # ➋
True
>>> t3 = (1, 2, 3)
>>> va == t3 # ➌
False
➊ 结果与之前一样,与预期相符。
➋ 结果与之前一样,但是为什么呢?稍后解释。8
➌ 结果不同了,这才是我们想要的。但是为什么会这样?请往下读……
这是因为示例3中的__eq__方法返回了NotImplemented。Vector实例与Vector2d实例比较时,具体步骤如下。
(1) 为了计算vc == v2d,Python调用Vector.eq(vc, v2d)。
(2) 经Vector.eq(vc, v2d)确认,v2d不是Vector实例,因此返回NotImplemented。
(3) Python得到NotImplemented结果,尝试调用Vector2d.eq(v2d, vc)。
(4) Vector2d.eq(v2d, vc)把两个操作数都变成元组,然后比较,结果是True
在示例4中,Vector实例和元组比较时,具体步骤如下。
(1) 为了计算va == t3,Python调用Vector.eq(va, t3)。
(2) 经Vector.eq(va, t3)确认,t3不是Vector实例,因此返回NotImplemented。
(3) Python得到NotImplemented结果,尝试调用tuple.eq(t3, va)。
(4) tuple.eq(t3, va)不知道Vector是什么,因此返回NotImplemented。
(5) 对==来说,如果反向调用返回NotImplemented,Python会比较对象的ID,作最后一搏。
增量赋值运算符
Vector类已经支持增量赋值运算符+=和*=了,如示例5所示。
#示例5:增量赋值不会修改不可变目标,而是新建实例,然后重新绑定
>>> v1 = Vector([1, 2, 3])
>>> v1_alias = v1 # ➊
>>>> id(v1) # ➋
4302860128
>>> v1 += Vector([4, 5, 6]) # ➌
>>> v1 # ➍
Vector([5.0, 7.0, 9.0])
>>> id(v1) # ➎
4302859904
>>> v1_alias # ➏
Vector([1.0, 2.0, 3.0])
>>> v1 *= 11 # ➐
>>> v1 # ➑
Vector([55.0, 77.0, 99.0])
>>> id(v1)
4302858336
➊ 复制一份,供后面审查Vector([1, 2, 3])对象。
➋ 记住一开始绑定给v1的Vector实例的ID。
➌ 增量加法运算。
➍ 结果与预期相符……
➎ ……但是创建了新的Vector实例。
➏ 审查v1_alias,确认原来的Vector实例没被修改。
➐ 增量乘法运算。
➑ 同样,结果与预期相符,但是创建了新的Vector实例。
如果实现了就地运算符方法,例如__iadd__,计算a += b的结果时会调用就地运算符方法。这种运算符的名称表明,它们会就地修改左操作数,而不会创建新对象作为结果。
#示例6:使用+运算符新建AddableBingoCage实例
>>> vowels = 'AEIOU'
>>> globe = AddableBingoCage(vowels) #➊
>>> globe.inspect()
('A', 'E', 'I', 'O', 'U')
>>> globe.pick() in vowels #➋
True
>>> len(globe.inspect()) #➌
4
>>> globe2 = AddableBingoCage('XYZ') #➍
>>>> globe3 = globe + globe2
>>> len(globe3.inspect()) #➎
7
>>> void = globe + [10, 20] #➏
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list'
➊ 使用5个元素(vowels中的各个字母)创建一个globe实例。
➋ 从中取出一个元素,确认它在vowels中。
➌ 确认globe的元素数量减少到4个了。
➍ 创建第二个实例,它有3个元素。
➎ 把前两个实例加在一起,创建第3个实例。这个实例有7个元素。
➏ AddableBingoCage实例无法与列表相加,抛出TypeError。那个错误消息是__add__方
法返回NotImplemented时Python解释器输出的。
#示例7:可以使用+=运算符载入现有的AddableBingoCage实例(接续示例13-16)
>>> globe_orig = globe ➊
>>> len(globe.inspect()) ➋
4
>>> globe += globe2 ➌
>>> len(globe.inspect())
7
>>> globe += ['M', 'N'] ➍
>>> len(globe.inspect())
9
>>> globe is globe_orig ➎
True
>>> globe += 1 ➏
Traceback (most recent call last):
...
TypeError: right operand in += must be 'AddableBingoCage' or an iterable
➊ 复制一份,供后面检查对象的标识。
➋ 现在globe有4个元素。
➌ AddableBingoCage实例可以从同属一类的其他实例那里接受元素。
➍ +=的右操作数也可以是任何可迭代对象。
➎ 在这个示例中,globe始终指代globe_orig对象。
➏ AddableBingoCage实例不能与非可迭代对象相加,错误消息会指明原因。
与+相比,+=运算符对第二个操作数更宽容。+运算符的两个操作数必须是相同类型(这里是AddableBingoCage),如若不然,结果的类型可能让人摸不着头脑。而+=的情况更明确,因为就地修改左操作数,所以结果的类型是确定的。
通过观察内置list类型的工作方式,我确定了要对+和+=的行为做什么限制。my_list + x只能用于把两个列表加到一起,而my_list += x可以使用右边可迭代对象x中的元素扩展左边的列表。list.extend()的行为也是这样的,它的参数可以是任何可迭代对象。
一般来说,如果中缀运算符的正向方法(如__mul__)只处理与self属于同一类型的操作数,那就无需实现对应的反向方法(如__rmul__),因为按照定义,反向方法是为了处理类型不同的操作数。
原文链接:https://blog.csdn.net/weixin_38492159/article/details/107715522

664

被折叠的 条评论
为什么被折叠?



