geekdoc-python-zh/docs/realpython/python-operators-expression...

25 KiB
Raw Permalink Blame History

Python 中的运算符和表达式

原文:https://realpython.com/python-operators-expressions/

在完成了本系列上一篇关于 Python 变量的教程之后,您现在应该已经很好地掌握了创建和命名不同类型的 Python 对象。让我们和他们一起工作吧!

以下是你将在本教程中学到的:你将看到如何在 Python 中对对象进行计算。本教程结束时,你将能够通过组合对象和操作符来创建复杂的表达式

***参加测验:***通过我们的交互式“Python 运算符和表达式”测验来测试您的知识。完成后,您将收到一个分数,以便您可以跟踪一段时间内的学习进度:

*参加测验

在 Python 中,操作符是一种特殊的符号,表示应该执行某种计算。运算符作用的值称为操作数

这里有一个例子:

>>> a = 10
>>> b = 20
>>> a + b
30

在这种情况下,+运算符将操作数ab相加。操作数可以是文字值,也可以是引用对象的变量:

>>> a = 10
>>> b = 20
>>> a + b - 5
25

一系列操作数和运算符,如a + b - 5被称为表达式。Python 支持许多将数据对象组合成表达式的操作符。下文将对此进行探讨。

算术运算符

下表列出了 Python 支持的算术运算符:

操作员 例子 意义 结果
+(一元) +a 一元正 a
换句话说,它并没有真正做什么。它的存在主要是为了完整,补充的一元否定
+(二元) a + b 加法 ab之和
-(一元) -a 一元否定 值等于a但符号相反
-(二元) a - b 减法 a中减去b
* a * b 乘法运算 ab的乘积
/ a / b 分部 a除以b的商。
结果总是有类型float
% a % b 模块 a除以b的余数
// a // b 楼层划分(也叫整数划分) a除以b的商,四舍五入到下一个最小的整数
** a ** b 求幂运算 a提高到b的幂

以下是这些运算符的一些使用示例:

>>> a = 4
>>> b = 3
>>> +a
4
>>> -b
-3
>>> a + b
7
>>> a - b
1
>>> a * b
12
>>> a / b
1.3333333333333333
>>> a % b
1
>>> a ** b
64

标准除法(/)的结果总是一个float,即使被除数能被除数整除:

>>> 10 / 5
2.0
>>> type(10 / 5)
<class 'float'>

当底数除法(//)的结果为正时,就好像小数部分被截断,只留下整数部分。当结果为负时,结果向下舍入到下一个最小(更大的负)整数:

>>> 10 / 4
2.5
>>> 10 // 4
2
>>> 10 // -4
-3
>>> -10 // 4
-3
>>> -10 // -4
2

顺便注意,在 REPL 会话中,只需在>>>提示符下键入表达式的值而不键入print(),就可以显示表达式的值,这与使用文字值或变量一样:

>>> 25
25
>>> x = 4
>>> y = 6
>>> x
4
>>> y
6
>>> x * 25 + y
106

Remove ads

比较运算符

操作员 例子 意义 结果
== a == b 等于 True如果a的值等于b
的值,否则
!= a != b 不等于 True如果a不等于b
否则False
< a < b 小于 True如果a小于b
否则
<= a <= b 小于或等于 True如果a小于或等于b
否则为False
> a > b 大于 True如果a大于b
否则
>= a >= b 大于或等于 True如果a大于或等于b
否则为False

下面是正在使用的比较运算符的例子:

>>> a = 10
>>> b = 20
>>> a == b
False
>>> a != b
True
>>> a <= b
True
>>> a >= b
False

>>> a = 30
>>> b = 30
>>> a == b
True
>>> a <= b
True
>>> a >= b
True

比较运算符通常用在布尔上下文中,如条件和循环语句,以指导程序流程,您将在后面看到。

浮点值的相等比较

回想一下之前关于浮点数的讨论,一个float对象内部存储的值可能并不完全是你所想的那样。因此,比较浮点值是否完全相等是不明智的做法。考虑这个例子:

>>> x = 1.1 + 2.2
>>> x == 3.3
False

呀!加法操作数的内部表示并不完全等于1.12.2,所以你不能依靠x来与3.3进行精确的比较。

确定两个浮点值是否“相等”的首选方法是,在给定一定容差的情况下,计算它们是否彼此接近。看一下这个例子:

>>> tolerance = 0.00001
>>> x = 1.1 + 2.2
>>> abs(x - 3.3) < tolerance
True

abs() 返回绝对值。如果两个数字之差的绝对值小于规定的公差,则它们足够接近,可以认为相等。

逻辑运算符

逻辑运算符notorand修改并连接在布尔上下文中评估的表达式,以创建更复杂的条件。

涉及布尔操作数的逻辑表达式

正如您所看到的Python 中的一些对象和表达式实际上是布尔类型的。也就是说,它们等于 Python 对象TrueFalse中的一个。考虑这些例子:

>>> x = 5
>>> x < 10
True
>>> type(x < 10)
<class 'bool'>

>>> t = x > 10
>>> t
False
>>> type(t)
<class 'bool'>

>>> callable(x)
False
>>> type(callable(x))
<class 'bool'>

>>> t = callable(len)
>>> t
True
>>> type(t)
<class 'bool'>

在上面的例子中,x < 10callable(x)t都是布尔对象或表达式。

当操作数为布尔型时,涉及notorand的逻辑表达式的解释很简单:

操作员 例子 意义
not not x True if xFalse
False if xTrue
(逻辑上颠倒了x的意义)
or x or y True如果x或者yTrue
False否则
and x and y True如果xy都是True
False否则

下面看看它们在实践中是如何工作的。

"not"和布尔操作数

x = 5
not x < 10
False
not callable(x)
True
操作数 价值 逻辑表达式 价值
x < 10 True not x < 10 False
callable(x) False not callable(x) True

"or"和布尔操作数

x = 5
x < 10 or callable(x)
True
x < 0 or callable(x)
False
操作数 价值 操作数 价值 逻辑表达式 价值
x < 10 True callable(x) False x < 10 or callable(x) True
x < 0 False callable(x) False x < 0 or callable(x) False

"and"和布尔操作数

x = 5
x < 10 and callable(x)
False
x < 10 and callable(len)
True
操作数 价值 操作数 价值 逻辑表达式 价值
x < 10 True callable(x) False x < 10 and callable(x) False
x < 10 True callable(len) True x < 10 or callable(len) True

Remove ads

布尔上下文中非布尔值的评估

很多对象和表达式不等于TrueFalse。尽管如此,它们仍然可以在布尔上下文中被评估,并被确定为“真”或“假”

那么什么是真的,什么不是?作为一个哲学问题,这超出了本教程的范围!

但是在 Python 中,它是定义明确的。在布尔上下文中评估时,以下所有内容都被视为假:

  • 布尔值False
  • 任何数值为零的值(00.00.0+0.0j)
  • 空字符串
  • 内置复合数据类型的对象为空(见下文)
  • Python 关键字 None 表示的特殊值

事实上Python 中内置的任何其他对象都被认为是真实的。

您可以使用内置的bool()函数来确定对象或表达式的“真实性”。如果参数为真,则bool()返回True,如果参数为假,则False返回。

数值

零值为假。 非零值为真。

>>> print(bool(0), bool(0.0), bool(0.0+0j))
False False False

>>> print(bool(-3), bool(3.14159), bool(1.0+1j))
True True True

字符串

空字符串为 false。 非空字符串为真。

>>> print(bool(''), bool(""), bool(""""""))
False False False

>>> print(bool('foo'), bool(" "), bool(''' '''))
True True True

内置复合数据对象

Python 提供了名为listtupledictset的内置复合数据类型。这些是包含其他对象的“容器”类型。如果一个对象是空的,那么它被认为是 false如果它不是空的那么它被认为是 true。

下面的例子为list类型演示了这一点。(列表是用方括号在 Python 中定义的。)

有关listtupledictset类型的更多信息,请参见即将到来的教程。

>>> type([])
<class 'list'>
>>> bool([])
False

>>> type([1, 2, 3])
<class 'list'>
>>> bool([1, 2, 3])
True

None关键字

None永远是假的:

>>> bool(None)
False

涉及非布尔操作数的逻辑表达式

非布尔值也可以通过notorand进行修改和连接。结果取决于操作数的“真实性”。

not”和非布尔操作数

下面是非布尔值x的情况:

如果x not x
“真实” False
“福尔西” True

以下是一些具体的例子:

>>> x = 3
>>> bool(x)
True
>>> not x
False

>>> x = 0.0
>>> bool(x)
False
>>> not x
True

or”和非布尔操作数

这是两个非布尔值xy的情况:

如果x x or y
真理 x
福尔西 y

注意,在这种情况下,表达式x or y的计算结果不是TrueFalse,而是xy中的一个:

>>> x = 3
>>> y = 4
>>> x or y
3

>>> x = 0.0
>>> y = 4.4
>>> x or y
4.4

即便如此,如果xy为真,则表达式x or y为真,如果xy都为假,则表达式为假。

and”和非布尔操作数

下面是两个非布尔值xy的结果:

如果x x and y
“真实” y
“福尔西” x
>>> x = 3
>>> y = 4
>>> x and y
4

>>> x = 0.0
>>> y = 4.4
>>> x and y
0.0

or一样,表达式x and y的计算结果不是TrueFalse,而是xy中的一个。如果xy都为真,则x and y为真,否则为假。

Remove ads

复合逻辑表达式和短路评估

到目前为止,您已经看到了只有一个orand操作符和两个操作数的表达式:

x or y
x and y

多个逻辑运算符和操作数可以串在一起形成复合逻辑表达式。

复合“or”表达式

考虑下面的表达式:

x1orT7】x2orx3or……xn

如果任一个xIT3】为真则该表达式为真。

在这样一个表达式中Python 使用了一种叫做短路评估的方法,也叫做麦卡锡评估,以纪念计算机科学家约翰·麦卡锡。从左到右依次对xIT5】操作数求值。一旦发现一个表达式为真就知道整个表达式为真。在这一点上Python 停止,不再计算任何术语。整个表达式的值是终止求值的 x i 的值。

为了帮助演示短路评估,假设您有一个简单的“身份”函数f(),其行为如下:

  • f()采用单一参数。
  • 它向控制台显示参数。
  • 它返回传递给它的参数作为返回值。

(您将在接下来的函数教程中看到如何定义这样的函数。)

f()的几个调用示例如下所示:

>>> f(0)
-> f(0) = 0
0

>>> f(False)
-> f(False) = False
False

>>> f(1.5)
-> f(1.5) = 1.5
1.5

因为f()简单地返回传递给它的参数,我们可以根据需要通过为arg指定一个适当的 true 或 falsy 值来使表达式f(arg)为 true 或 falsy。另外f()向控制台显示它的参数,控制台直观地确认它是否被调用。

现在,考虑下面的复合逻辑表达式:

>>> f(0) or f(False) or f(1) or f(2) or f(3)
-> f(0) = 0
-> f(False) = False
-> f(1) = 1
1

解释器首先评估f(0),也就是00的数值为假。表达式还不为真,所以计算从左到右进行。下一个操作数f(False)返回False。这也是错误的,所以评估还在继续。

接下来是f(1)。计算结果为1,这是真的。此时,解释器停止,因为它现在知道整个表达式为真。1作为表达式的值返回,其余的操作数f(2)f(3)永远不会被计算。从显示屏上可以看到f(2)f(3)呼叫没有发生。

复合“and”表达式

具有多个and运算符的表达式中也存在类似的情况:

x1andT7】x2andx3and……xn

如果所有的xIT3】都为真则该表达式为真。

在这种情况下,短路求值决定了一旦发现任何操作数为假,解释器就停止求值,因为此时整个表达式都被认为是假的。一旦出现这种情况,就不再计算操作数,并且终止计算的 falsy 操作数作为表达式的值返回:

>>> f(1) and f(False) and f(2) and f(3)
-> f(1) = 1
-> f(False) = False
False

>>> f(1) and f(0.0) and f(2) and f(3)
-> f(1) = 1
-> f(0.0) = 0.0
0.0

在上面的两个例子中,求值在第一个为假的词处停止——第一个例子是f(False),第二个例子是f(0.0)——f(2)f(3)调用都没有发生。False0.0分别作为表达式的值返回。

如果所有的操作数都是真的,它们都会被求值,最后一个(最右边的)操作数作为表达式的值返回:

>>> f(1) and f(2.2) and f('bar')
-> f(1) = 1
-> f(2.2) = 2.2
-> f(bar) = bar
'bar'

Remove ads

利用短路评估的习惯用法

有一些常见的惯用模式利用短路评估来简化表达。

避免异常

假设你定义了两个变量ab,你想知道(b / a) > 0:

>>> a = 3
>>> b = 1
>>> (b / a) > 0
True

但是你需要考虑到a可能是0的可能性,在这种情况下,解释器将引发一个异常:

>>> a = 0
>>> b = 1
>>> (b / a) > 0
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    (b / a) > 0
ZeroDivisionError: division by zero

您可以使用这样的表达式来避免错误:

>>> a = 0
>>> b = 1
>>> a != 0 and (b / a) > 0
False

a0时,a != 0为假。短路评估确保评估在该点停止。(b / a)不被评估,也不引发错误。

事实上,你可以更简洁。当a0时,表达式a本身就是假的。不需要明确的比较a != 0:

>>> a = 0
>>> b = 1
>>> a and (b / a) > 0
0

选择默认值

另一种习语包括当指定值为零或为空时选择默认值。例如,假设您想将一个变量s赋给包含在另一个名为string的变量中的值。但是如果string是空的,你需要提供一个默认值。

下面是使用短路评估表达这一点的简明方法:

s = string or '<default_value>'

如果string非空,则为真,此时表达式string or '<default_value>'为真。求值停止,string的值被返回并赋给s:

>>> string = 'foo bar'
>>> s = string or '<default_value>'
>>> s
'foo bar'

另一方面,如果string是一个空的字符串,那么它就是 falsy。string or '<default_value>'的求值继续到下一个操作数'<default_value>',该操作数被返回并赋值给s:

>>> string = ''
>>> s = string or '<default_value>'
>>> s
'<default_value>'

Remove ads

链式比较

比较运算符可以任意长度链接在一起。例如,以下表达式几乎是等效的:

x < y <= z
x < y and y <= z

它们都将计算出相同的布尔值。两者的细微区别在于,在链式比较x < y <= z中,y只被求值一次。更长的表达式x < y and y <= z将导致y被求值两次。

**注意:**在y是静态值的情况下,这不是一个显著的区别。但是考虑一下这些表达式:

x < f() <= z
x < f() and f() <= z

如果f()是一个导致程序数据被修改的函数,那么它在第一种情况下被调用一次和在第二种情况下被调用两次之间的差别可能很重要。

更一般的是,如果 op 1 op 2 …,***opn***是比较运算符,那么下面的具有相同的布尔值:

x1**T3】op1**x2***op2***x3…xn-1***opn***xn

x1**T5】op1**x2andx2***op2***x3and…xn-1***opn***xn

在前一种情况下,每个 x i 只计算一次。在后一种情况下,除了第一次和最后一次,每个都将被评估两次,除非短路评估导致过早终止。

按位运算符

按位运算符将操作数视为二进制数字序列,并对其进行逐位运算。支持以下运算符:

操作员 例子 意义 结果
& a & b 按位 结果中的每个位位置是操作数相应位置的位的逻辑。(1如果两者都是1,否则为0。)
&#124; a &#124; b 按位 结果中的每个位位置是操作数相应位置的位的逻辑。(1如果任一个为1,否则为0。)
~ ~a 按位求反 结果中的每个位位置都是操作数相应位置的位的逻辑反。(1如果00如果1)。)
^ a ^ b 按位异或 结果中的每个位位置是操作数的相应位置中的位的逻辑异或。(1如果操作数中的位不同,0如果相同。)
>> a >> n 右移 n 地点 每一位都右移n位。
<< a << n 左移 n 位置 每一位左移n位。

以下是一些例子:

>>> '0b{:04b}'.format(0b1100 & 0b1010)
'0b1000'
>>> '0b{:04b}'.format(0b1100 | 0b1010)
'0b1110'
>>> '0b{:04b}'.format(0b1100 ^ 0b1010)
'0b0110'
>>> '0b{:04b}'.format(0b1100 >> 2)
'0b0011'
>>> '0b{:04b}'.format(0b0011 << 2)
'0b1100'

注意:'0b{:04b}'.format()的目的是格式化按位运算的数字输出,使它们更容易阅读。稍后您将看到format()方法的更多细节。现在,只需注意按位运算的操作数和结果。

标识运算符

Python 提供了两个操作符isis not,它们决定了给定的操作数是否具有相同的身份——也就是说,引用同一个对象。这与相等不是一回事,相等意味着两个操作数引用包含相同数据的对象,但不一定是同一对象。

以下是两个相等但不相同对象的示例:

>>> x = 1001
>>> y = 1000 + 1
>>> print(x, y)
1001 1001

>>> x == y
True
>>> x is y
False

这里的xy都是指值为1001的对象。他们是平等的。但是它们不引用同一个对象,您可以验证:

>>> id(x)
60307920
>>> id(y)
60307936

xy没有相同的身份,x is y返回False

您之前已经看到,当您进行类似于x = y的赋值时Python 仅仅创建了对同一对象的第二个引用,并且您可以使用id()函数来确认这一事实。您也可以使用 is操作员进行确认:

>>> a = 'I am a string'
>>> b = a
>>> id(a)
55993992
>>> id(b)
55993992

>>> a is b
True
>>> a == b
True

在这种情况下,由于ab引用同一个对象,因此ab也应该相等。

不出所料,is的反义词是is not:

>>> x = 10
>>> y = 20
>>> x is not y
True

Remove ads

运算符优先级

考虑这个表达式:

>>> 20 + 4 * 10
60

这里有歧义。Python 是否应该先执行加法20 + 4,然后将和乘以10?还是应该先执行乘法4 * 10,然后再执行加法20

很明显,既然结果是60Python 选择了后者;如果它选择了前者,结果将是240。这是标准的代数过程,几乎在所有编程语言中都可以找到。

该语言支持的所有运算符都被赋予一个优先级。在表达式中,首先执行所有优先级最高的运算符。一旦获得这些结果,就执行下一个最高优先级的运算符。如此继续下去,直到表达式被完全求值。任何优先级相同的运算符都按从左到右的顺序执行。

以下是到目前为止您所看到的 Python 操作符的优先级顺序,从最低到最高:

  操作员 描述
最低优先级 or 布尔或
and 布尔与
not 布尔 NOT
==!=<<=>>=isis not 比较,身份
&#124; 按位或
^ 按位异或
& 按位 AND
<<>> 比特移位
+- 加法、减法
*///% 乘法、除法、除法、
+x-x~x 一元正、一元负、按位负
最高优先级 ** 求幂

位于表顶部的运算符优先级最低,位于表底部的运算符优先级最高。表中同一行的任何运算符都具有相同的优先级。

在上面的例子中,为什么先执行乘法是显而易见的:乘法的优先级高于加法。

同样,在下面的例子中,3先被提升到4的幂,等于81,然后乘法按从左到右的顺序进行(2 * 81 * 5 = 810):

>>> 2 * 3 ** 4 * 5
810

可以使用括号覆盖运算符优先级。括号中的表达式总是首先执行,在没有括号的表达式之前。因此,会发生以下情况:

>>> 20 + 4 * 10
60
>>> (20 + 4) * 10
240
>>> 2 * 3 ** 4 * 5
810
>>> 2 * 3 ** (4 * 5)
6973568802

在第一个示例中,首先计算20 + 4,然后将结果乘以10。在第二个例子中,首先计算4 * 5,然后将3提升到那个幂,然后将结果乘以2

自由使用括号没有错,即使它们不需要改变求值的顺序。事实上,这被认为是很好的实践,因为它可以使代码更具可读性,并且使读者不必从记忆中回忆运算符优先级。请考虑以下情况:

(a < 10) and (b > 30)

这里括号是完全不必要的,因为比较操作符比and具有更高的优先级,而且无论如何都是先执行的。但是有些人可能认为带括号版本的意图比不带括号的版本更明显:

a < 10 and b > 30

另一方面,可能有些人更喜欢后者;这是个人喜好的问题。关键是,如果你觉得括号能让代码更易读,你可以一直使用它,即使它们不需要改变求值的顺序。

Remove ads

扩充赋值运算符

你已经看到了一个等号(=)被用来给一个变量赋值。当然,赋值右边的值是包含其他变量的表达式是完全可行的:

>>> a = 10
>>> b = 20
>>> c = a * 5 + b
>>> c
70

事实上,赋值右边的表达式可以包含对被赋值变量的引用:

>>> a = 10
>>> a = a + 5
>>> a
15

>>> b = 20
>>> b = b * 3
>>> b
60

第一个例子被解释为“a被赋予当前值a加上5,”实际上通过5增加了a的值。第二个读数为“b被赋予当前值为b乘以3,实际上将b的值增加了三倍。

当然,这种赋值只有在变量已经被赋值的情况下才有意义:

>>> z = z / 12
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    z = z / 12
NameError: name 'z' is not defined

Python 支持这些算术运算符和按位运算符的简化扩充赋值符号:

算术 按位
+
-
*
/
%

** | & &#124; ^ >> T4】 |

对于这些运算符,以下内容是等效的:

x <op>= y
x = x <op> y

看看这些例子:

| 扩充的 赋值 | | 标准

分配
a += 5 相当于 a = a + 5
a /= 10 相当于 a = a / 10
a ^= b 相当于 a = a ^ b

结论

在本教程中,您了解了 Python 支持的多种多样的操作符来将对象组合成表达式

到目前为止,您看到的大多数例子都只涉及简单的原子数据,但是您看到了对 string 数据类型的简要介绍。下一篇教程将更详细地探索字符串对象。

***参加测验:***通过我们的交互式“Python 运算符和表达式”测验来测试您的知识。完成后,您将收到一个分数,以便您可以跟踪一段时间内的学习进度:

参加测验

« Variables in PythonOperators and Expressions in PythonStrings in Python »********