在 Python 语言参考手册中,“6.5. Unary arithmetic and bitwise operations”一节 2(https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations)列出了三个一元运算符。下面是这三个运算符和对应的特殊方法。
2现有版本是 6.6 节,而不是 6.5 节。——编者注
-(__neg__)
一元取负算术运算符。如果 x 是 -2,那么 -x == 2。
+(__pos__)
一元取正算术运算符。通常,x == +x,但也有一些例外。如果好奇,请阅读“x 和 +x 何时不相等”附注栏。
~(__invert__)
对整数按位取反,定义为 ~x == -(x+1)。如果 x 是 2,那么 ~x == -3。
Python 语言参考手册中的“Data Model”一章(https://docs.python.org/3/reference/datamodel.html#object.__neg__ )还把内置的 abs(...) 函数列为一元运算符。它对应的特殊方法是 __abs__,从 1.2.1 节起已经见过多次。
支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有一个参数,self。然后,使用符合所在类的逻辑实现。不过,要遵守运算符的一个基本规则:始终返回一个新对象。也就是说,不能修改 self,要创建并返回合适类型的新实例。
对 - 和 + 来说,结果可能是与 self 同属一类的实例。多数时候,+ 最好返回 self 的副本。abs(...) 的结果应该是一个标量。但是对 ~ 来说,很难说什么结果是合理的,因为可能不是处理整数的位,例如在 ORM 中,SQL WHERE 子句应该返回反集。
如前所述,我们将为第 10 章定义的 Vector 类实现几个新运算符。示例 13-1 列出了示例 10-16 实现的 __abs__ 方法,以及新增加的 __neg__ 和 __pos__ 一元运算符方法。
示例 13-1 vector_v6.py:把一元运算符
-和+添加到示例 10-16 中
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __neg__(self):
return Vector(-x for x in self) ➊
def __pos__(self):
return Vector(self) ➋
❶ 为了计算 -v,构建一个新 Vector 实例,把 self 的每个分量都取反。
❷ 为了计算 +v,构建一个新 Vector 实例,传入 self 的各个分量。
还记得吗? Vector 实例是可迭代的对象,而且 Vector.__init__ 的参数是一个可迭代对象,因此 __neg__ 和 __pos__ 的实现短小精悍。
我们不打算实现 __invert__ 方法,因此如果用户在 Vector 实例上尝试计算 ~v,Python 会抛出 TypeError,而且输出明确的错误消息,“bad operand type for unary ~: 'Vector'”。
下述附注栏讨论一个奇怪的问题,能增长你的 + 一元运算符知识。接下来的重要话题是:重载向量加法运算符 +(见 13.3 节)。
x和+x何时不相等每个人都觉得
x == +x,而且在 Python 中,几乎所有情况下都是这样。但是,我在标准库中找到两例x != +x的情况。第一例与
decimal.Decimal类有关。如果x是Decimal实例,在算术运算的上下文中创建,然后在不同的上下文中计算+x,那么x != +x。例如,x所在的上下文使用某个精度,而计算+x时,精度变了,如示例 13-2 所示。示例 13-2 算术运算上下文的精度变化可能导致
x不等于+x>>> import decimal >>> ctx = decimal.getcontext() ➊ >>> ctx.prec = 40 ➋ >>> one_third = decimal.Decimal('1') / decimal.Decimal('3') ➌ >>> one_third ➍ Decimal('0.3333333333333333333333333333333333333333') >>> one_third == +one_third ➎ True >>> ctx.prec = 28 ➏ >>> one_third == +one_third ➐ False >>> +one_third ➑ Decimal('0.3333333333333333333333333333')❶ 获取当前全局算术运算的上下文引用。
❷ 把算术运算上下文的精度设为 40。
❸ 使用当前精度计算 1/3。
❹ 查看结果,小数点后有 40 个数字。
❺
one_third == +one_third返回True。❻ 把精度降低为 28,这是 Python 3.4 为
Decimal算术运算设定的默认精度。❼ 现在,
one_third == +one_third返回False。❽ 查看
+one_third,小数点后有 28 个数字。虽然每个
+one_third表达式都会使用one_third的值创建一个新Decimal实例,但是会使用当前算术运算上下文的精度。
x != +x的第二例在collections.Counter的文档中(https://docs.python.org/3/library/collections.html#collections.Counter)。Counter类实现了几个算术运算符,例如中缀运算符+,作用是把两个Counter实例的计数器加在一起。然而,从实用角度出发,Counter相加时,负值和零值计数会从结果中剔除。而一元运算符+等同于加上一个空Counter,因此它产生一个新的Counter且仅保留大于零的计数器。见示例 13-3。示例 13-3 一元运算符
+得到一个新Counter实例,但是没有零值和负值计数器3>>> ct = Counter('abracadabra') >>> ct Counter({'a': 5, 'r': 2, 'b': 2, 'd': 1, 'c': 1}) >>> ct['r'] = -3 >>> ct['d'] = 0 >>> ct Counter({'a': 5, 'b': 2, 'c': 1, 'd': 0, 'r': -3}) >>> +ct Counter({'a': 5, 'b': 2, 'c': 1})下面回归正题。
3应该在最前面加一行:>>> from collections import Counter。——编者注