5.5 操作符

数值类型可进行多种运算。从标准操作符到数值操作符,甚至还有专门的整型操作符。

5.5.1 混合模式操作符

也许你还记得,过去将两个数相加时,你必须努力保证操作数是合适的类型。自然地,加法总是使用+号,然而在计算机语言看来这件事没那么简单,因为数字又有很多不同的类型。

当两个整型相加时,+号表示整型加法,当两个浮点型相加时,+表示浮点型加法,依此类推。在Python中,甚至非数字类型也可以使用+操作符。举例来说,字符串A+字符串B并不表示加法操作,它表示的是把这两个字符串连接起来,生成一个新的字符串。关键之处在于支持+操作符的每种数据类型,必须告诉Python, +操作符应该如何去工作。这也体现了重载概念的具体应用。

虽然我们不能让一个数字和一个字符串相加,但Python确实支持不同的数字类型相加。当一个整型和一个浮点型相加时,系统会决定使用整型加法还是浮点型加法(实际上并不存在混合运算)。Python使用数字类型强制转换的方法来解决数字类型不一致的问题,也就是说它会强制将一个操作数转换为同另一个操作数相同的数据类型。这种操作不是随意进行的,它遵循以下基本规则。

首先,如果两个操作数都是同一种数据类型,没有必要进行类型转换。仅当两个操作数类型不一致时,Python才会去检查一个操作数是否可以转换为另一类型的操作数。如果可以,转换它并返回转换结果。由于某些转换是不可能的,比如将一个复数转换为非复数类型,将一个浮点型转换为整型等,因此转换过程必须遵守几个规则。

要将一个整型转换为浮点型,只要在整型后面加个“.0“就可以了。要将一个非复数转换为复数,则只需要要加上一个“0j“的虚数部分。这些类型转换的基本原则是:整型转换为浮点型,非复数转换为复数。在Python语言参考中这样描述coerce()方法:

  • 如果有一个操作数是复数,另一个操作数被转换为复数;

  • 否则,如果有一个操作数是浮点型,另一个操作数被转换为浮点型;

  • 否则,如果有一个操作数是长整型,则另一个操作数被转换为长整型;

  • 否则,两者必然都是普通整型,无须类型转换。(参见下文中的示意图)。

图5-1的流程图阐释了强制转换的规则。数字类型之间的转换是自动进行的,程序员无须自己编码处理类型转换。不过在确实需要明确指定对某种数据类型进特殊类型转换的场合,Python提供了 coerce()内建函数来帮助你实现这种转换。(见5.6.2小节)

下面演示一下Python的自动数据类型转换。为了让一个整型和一个浮点型相加,必须使二者转换为同一类型。因为浮点型是超集,所以在运算开始之前,整型必须强制转换为一个浮点型,运算结果也是浮点型。

5.5 操作符 - 图1

5.5 操作符 - 图2

图 5-1 数值类型转换

5.5.2 标准类型操作符

第4章中讲到的标准操作符都可以用于数值类型。上文中提到的混合模式运算问题,也就是不同数据类型之间的运算,在运算之前,Python内部会将两个操作数转换为同一数据类型。

下面是一些数字标准运算的例子。

5.5 操作符 - 图3

5.5.3 算术操作符

Python支持单目操作符正号(+)和负号(一);双目操作符+、一、、/、%和*,分别表示加法、减法、乘法、除法、取余和幂运算。从Python2.2起,还增加了一种新的整除操作符//。

1.除法

拥有C背景的程序员一定熟悉传统除法——也就是说,对整型操作数,会执行“地板除”(floor,取比商小的最大整型。例如5除以2等于2.5,其中“2”就称为商的“地板”,即“地板除”的结果。本书中使用“地板除”的说法是为了沿用原作者的风格,译者注)。对浮点操作数会执行真正的除法。然而,对第一次学编程的人或者那些依赖精确计算的人来说,可能就需要多次调整代码才能得到自己想要的结果。

在未来的Python版本中,Python开发小组已经决定改变/操作符的行为。/的行为将变更为真正的除法,会增加一种新的运算来表示地板除。下面我们总结一下Python现在的除法规则,以及未来的除法规则。

传统除法

如果是整型除法,传统除法会舍去小数部分,返回一个整型(地板除)。如果操作数之一是浮点型,则执行真正的除法。包括Python语言在内的很多语言都是这种行为。请看下面的例子。

5.5 操作符 - 图4

真正的除法

除法运算总是返回真实的商,不管操作数是整型还是浮点型。在未来版本的Python中,这将是除法运算的标准行为。现阶段通过执行from_fiiture_import division指令,也可以做到这一点。

5.5 操作符 - 图5

地板除

从Python 2.2开始,一个新的操作符//已经被增加进来,以执行地板除://除法不管操作数为何种数值类型,总是舍去小数部分,返回数字序列中比真正的商小的最接近的数字。

5.5 操作符 - 图6

关于除法运算的变更,支持的人和反对的人几乎一样多。有些人认为这种变化是错误的,有些人则不想修改自己的现有代码,而剩下的人则想要真正的除法。

之所以会有这种变化是因为Python的核心开发团队认为Python的除法运算从一开始就设计失误。特别是,随着Python的逐渐发展,它已经成为那些从未接触过地板除的人们的首选学习语言。Python语言的创始人在他的“What‘s New in Python 2.2“一文中讲到:

5.5 操作符 - 图7

你可能会说,只要有一个参数为浮点型这个函数就能正常工作。像上面提到的那样,要确保它能正常工作需要强制将参数转换为浮点类型,也就是rate = float(distance)/float(totalTime)。将来除法将转变为真正的除法,上面的代码可以无需更改正常工作。需要地板除的地方只需要改变为两个连续的除号。

是的,代码会受到一些影响,Python团队己经创作了一系列脚本来帮助你转换旧代码,以确保它能适应新的除法行为。而且对那些强烈需要某种除法行为的人来说,Python解释器提供了 Qdivision_style启动参数。-Qriew执行新的除法行为,-Qold则执行传统除法行为(默认为Qold)。你也可以帮ã你的用户使用-Qwarn或-Qwamall参数度过过渡时期。

关于这次变化的详细信息可以参考PEP238。如果你对这场论战感兴趣,也可以翻阅2001年的 comp.lang.python归档。表5.2总结了除法操作符在不同Python版本中的行为差异。

5.5 操作符 - 图8

2.取余

整型取余相当容易理解,浮点型取余就略复杂些。

商取小于等于精确值的最大整型的乘积之差。即:x-(math.floor(x/y)*y)或者

5.5 操作符 - 图9

对于复数,取余的定义类似于浮点型,不同之处在于商仅取其实数部分,即x-(math.floor<(x/y).real)*y)。

3.幂运算

幂运算操作符和一元操作符之间的优先级关系比较特别:幂运算操作符比其左侧操作数的一元操作符优先级低,比其右侧操作数的一元操作符的优先级高,由于这个特性你会在算术操作符表中找到两个**。下面举几个例子:

5.5 操作符 - 图10

第2种情况下解释器先计算32再取其相反数,我们需要给“-3”加上括号来得到我们希望的结果。最后一个例子,结果是4(-1),这是按照规定的优先级获得的结果。

注意1/4作为整型除法结果是0所以以整型为底进行负数指数运算会引发一个negative power (负数指数)异常。

5.5 操作符 - 图11

5.5 操作符 - 图12

4.总结

表5.3总结了所有的算术操作符,从上到下,计算优先级依次降低。这里列出的所有操作符都比即将在5.5.4小节讲到的位操作符优先级高。

5.5 操作符 - 图13

下面是更多Python数值运算的例子:

5.5 操作符 - 图14

5.5 操作符 - 图15

注意指数操作符的优先级高于连接实部和虚部的+号操作符。就上面最后一个例子来说,我们人为的加上了括号,这就改变运算顺序,从而得到我们想要的结果。

5.5.4 位操作符(只适用于整型)

Python整型支持标准位运算:取反(〜),按位与(&)、或(|)和异或(^),以及左移(<<)和右移(>>)。Python这样处理位运算。

  • 负数会被当成正数的2进制补码处理。

  • 左移和右移N位等同于无溢出检查的2的N次幂运算:2**N。

  • 对长整型来说,位操作符使用一种经修改的2进制补码形式,使得符号位可以无限向左扩展。

取反(〜)运算的优先级与数字单目操作符相同,是所有位操作符中优先级最高的一个。左移和右移运算的优先级次之,但低于加减法运算。与、或、异或运算优先级最低。所有位操作符按优先级高低列在表5.4中。

5.5 操作符 - 图16

下面是几个使用整型30, 45, 60进行位运算的例子。

5.5 操作符 - 图17

5.5 操作符 - 图18