RealPython 基础教程:运算符和表达式

​“ 运算是程序的基本功能,而运算符和表达式则是最基本的运算单元

在了解了不同类型变量之后,我们现在该用这些变量做点什么了。

今天,我们将了解如何在变量上执行计算。你最终将会掌握如何使用变量和操作符来创建复杂的表达式。

Python 中,运算符(operators)是一些特殊的符号,用来指明可以执行某种计算。

那些被运算符操作的值叫做操作数(operands)。

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

这个简单的例子中,运算符 + 将 a 和 b 两个操作数相加。

操作数既可以是字面常量,也可以是指向对象的变量。

>>> a + b -5
25

像 a + b – 5 这样由运算符和操作数组成的序列称为表达式。

Python 提供了众多的运算符来将各种对象组成表达式。

我们按照运算类型分别介绍一下这些运算符。


【算术运算符】

顾名思义,算术运算符是用来做算术运算的。

下表列举了 Python 支持的算术运算符。

运算符 例子 用途 运算结果
+(一元) +a 对操作数取正 a,无实际意义
+(二元) a + b 操作数相加 a 和 b 之和
-(一元) -a 对操作数取负 a的负数
-(二元) a – b 操作数相减 a 和 b 之差
* a * b 操作数相乘 a 和 b 之积
/ a / b 操作数相除 a 除以 b 的商,结果为 float 类型
% a % b 求模 a 除以 b 的余数
// a // b 地板除(floor division):
两数相除,结果向下取整
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
>>> a // b
1

除法(/)运算的结果始终为 float 类型,即使可以整除。

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

对于地板除(floor division),若结果为正,小数部分被舍弃;若结果为负,结果被约等为和其值最接近的不大于该值的整数。你可以这样统一理解,地板除的结果就是取数轴上结果点左侧最靠近它的那个整数。

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

【比较运算符】

比较运算符用于比较两个对象的“大小”。不同对象有其自定义的“大小”语义。

下表列举了 Python 支持的比较运算符。

运算符 例子 用途 运算结果
== a == b 比较操作数是否相等 True,若 a 等于 b;False,若不相等。
!= a != b 比较操作数是否不等 True,若 a 不等于 b;False,若相等
< a < b 小于比较 True,若 a 小于 b;False,若不小于
<= a <= b 小于或等于比较 True,若 a 小于或等于 b;False,若大于
> a > b 大于比较 True,若 a 大于 b;False,若不大于
>= a >= b 大于或等于比较 True,若 a 大于或等于 b;False,若小于

看一些例子:

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

比较运算符通常用于 Boolean 上下文中,比如条件和循环语句,以改变程序的控制流程。

这里需要注意一下浮点数的等值比较。

我们在 Python 变量中介绍过,存储在 float 对象中的数值可能并非你所想的那么精确。因此,直接比较两个浮点数是否相等,通常是不可取的操作。

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

正确的做法是:比较两个浮点数是否足够接近彼此,它们的“距离”在可接受的误差范围内即认为两者相等。

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

【逻辑运算符】

逻辑运算符包括:not、or 和 and。这三者可连接和修改 Boolean 上下文中的表达式,从而表达更复杂的条件语义。

1,包含 Boolean 类型操作数的逻辑表达式

我们知道,Python 中有些对象和表达式的值可以是 True 或 False,这时候,这些对象和表达式实际上就是 Boolean 类型。

>>> 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<10、callable(x)、t 都是 Boolean 类型的对象或表达式。

当表达式中含有这些 Boolean 类型的操作数时,计算表达式的值很简单。

可根据下表来计算:

运算符 例子 含义
not not x True,若 x 是False;False,若 x 是 True
or x or y True,若 x 或 y 是 True;否则,False
and x and y True,若 x 和 y 均为 True;否则,False

看一些例子:

>>> x = 5
>>> x < 10
True
>>> not x < 10
False
>>> callable(x)
False
>>> not callable(x)
True
>>>
>>> x < 10 or callable(x)
True
>>> x < 0 or callable(x)
False
>>>
>>> x < 10 and callable(x)
False
>>> x < 10 and callable(len)
True

2,非 Boolean 类型的值在 Boolean 上下文中的求值

Python 中还有很多对象和表达式的值并不等于 True 或 False,即它们非 Boolean 类型。

尽管如此,在需要进行 Boolean 计算的环境中,这些对象或表达式也可以被适当处理,从而被视为“真值(truthy)”或“假值(falsy)”。

那么,到底何为真?何为假?嗯,这可以上升为一个较难回答的哲学问题。

但在 Python 中,真与假却是良好定义的。

在 Boolean 上下文中,以下这些情况均视作假:

  • Boolean 类型值:False
  • 任何在数字上等于0的值:0、0.0、0.0+0.0j
  • 空字符串:”
  • 任何空的内置组合数据类型
  • 关键字 None 表示的值

其他 Python 内置的对象可判为真。

我们可以使用 bool() 函数来判断一个对象或表达式是否为真。若参数为真值,bool() 返回 True,否则返回 False。

数字数值的 Boolean 判定方法:

值为 0 的数字为 False,非 0 值为 True。

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

字符串的 Boolean 判定方法:

空字符串为 False,非空字符串为 True。

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

内置组合类型对象的 Boolean 判定方法:

Python 内置的组合数据类型包括:list、tuple、dict 和 set。它们是可以包含其他对象的“容器”。

当这些容器不含任何其他对象时,这些容器就是空的。若容器为空,容器对象就视作假;容器非空,容器对象就视作真。

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

关键字 None 永远为假。

>>> bool(None)
False

3,包含非 Boolean 类型操作数的逻辑表达式

非 Boolean 类型的值也可用在逻辑表达式中,通过 not、or 和 and 来修改或组合。表达式的计算结果依赖于这些非 Boolean 操作数的真假。

注意:这里,表达式的结果不一定是 Boolean 值!

not 作用于非 Boolean 操作数:

当 x 为: not x 为:
真值 False
假值 True

例如:

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

or 作用于非 Boolean 操作数:

当 x 为: x or y 为:
真值 x
假值 y

例如:

>>> x = 3
>>> y = 4
>>> x or y
3
>>> x = 0.0
>>> y = 4.4
>>> x or y
4.4

and 作用于非 Boolean操作数:

当 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

4,复合表达式与短路求值

我们在上文列举的例子都是使用了一个运算符和至多两个操作数:

x or y
x and y

实际上,多个运算符和操作数也可以连在一起使用,形成复合逻辑表达式。

复合 or 表达式形式如下:

x1 or x2 or x3 or ... xn

当 xi 中任一操作数为True 时,表达式为 True。

在处理这类包含多个逻辑运算符的表达式时,Python 采用“短路求值”法来计算表达式的值。解释器会从左向右逐一计算每个操作数 xi 的值。一旦遇到一个 xi 的值为 True,整个表达式就被认为是 True,此时,解释器不再继续向右计算,表达式的值就是最后那个已计算的 xi 操作数的值

请注意体会“表达式的真假值”和“表达式的值”的区别,我们有时候会混用这两个概念。

为便于理解“短路求值”,我们可以设计一个简单的函数 f(),该函数的功能为:

  • f() 接受一个单一的值作为参数
  • 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() 传递具有真值或假值的参数,以使得 f(arg) 的值也为真或假。并且,通过控制台的输出,我们能看到复合逻辑表达式中某一部分是否被调用了。

来看下边这个复合逻辑表达式:

>>> 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)、f(False) 依次被调用,直到 f(1) 为 True 时,表达式已能被判定为 True,计算到此结束,表达式的值就是 f(1) 的值。f(2) 和 f(3) 不会被执行到。

复合 and 表达式形式如下:

x1 and x2 and x3 and ... xn

所有 xi 均为 True,表达式才为 True。

短路求值对复合 and 表达式的处理逻辑为:从左到右依次计算每个操作数的真假值,一旦遇到一个 xi 的值为 False,整个表达式就被判定为 False,计算结束,表达式的值就是最后那个已计算的 xi 操作数的值。

仍使用 f() 函数来看两个例子:

>>> 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),表达式的值分别为 False 和 0.0。后边的 f(2) 和 f(3) 没有被调用。

如果所有的操作数都是真值,那么它们都会被计算,最后一个操作数的值就是表达式的值。

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

短路求值在实际应用中有一些惯用场景:

  • 避免出现异常假设有两个变量:a 和 b。我们想要判断 (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 "<stdin>", line 1,
     in <module>ZeroDivisionError: division by zero

    我们可借助短路求值避免这种异常:

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

    a 为 0 时,a != 0 为假值,and 表达式求值结束,右边的除法运算不会执行到。

    对于这个问题,我们还可以写出更简单的表达式:

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

    作为数字,a 为 0 时,其就是一个假值,and 表达式也会结束求值过程。

  • 选择默认值为变量赋值时,若遇到 0 或 空值,可借助短路求值为变量赋一个默认值。

    比如,我们想使用字符串 s2 为字符串 s1 赋值,如果 s2 为空字符串,我们可以为 s1 指定一个默认值。

    >>> s2 = "World"
    >>> s1 = s2 or 'Hello'
    >>> s1
    'World'
    >>>
    >>> s2 = ''
    >>> s1 = s2 or 'Hello'
    >>> s1
    'Hello'

5,链式比较

在 Python 中,多个比较运算符可以串联起来使用。

比如,下边这两个表达式基本相同:

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

它们有一点区别:y 的计算次数不同。x < y <= z 中,y 只计算一次;x < y and y <= z 中,y 会被计算两次。

如果 y 只是一个静态值,比如 1,这种差别微乎其微。但假如 y 是一个复杂的表达式,少一次计算可能会带来可观的效率上的提升。例如:

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

更一般的情况,如果 op1、op2、op…、opn 是比较运算符,那么下边的表达式具有相同的 Boolean 值:

x1 op1 x2 op2 x3 ... opn xn
x1 op1 x2 and x2 op2 x3 and x3 ... opn xn

同样,前者中的 xi 只计算一次,而后者中的非首尾的 xi 会计算两次,除非计算提前结束。


【位操作运算符】

位操作运算符将操作数视为二进制序列,对操作数进行逐位运算。

下表列举了 Python 支持的位操作运算符。

运算符 例子 含义 结果
& a & b 按位与 两个操作数对应位相与(全1则1,否则为0)
| a | b 按位或 两个操作数对应位相或(有1则1,全0为0)
~ ~a 取反 对操作数的每一位取反(0则1,1则0)
^ a ^ b 异或 两个操作数对应位异或(异则1,同则0)
>> a >> n 右移 将操作数右移n位
<< a << 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'

这里,以二进制的形式来表示操作数和运算结果,可以清晰地看出位运算的执行逻辑。


【ID 运算符】

id 运算符用于判断两个操作数是否拥有相同 id,也即是否指向同一对象。

这和“相等”不是同一个概念,相等表示两个操作数的值相等,而它们不一定指向同一个对象。

Python 提供 is 和 is not 两个 id 运算符。

看一些例子:

>>> a = 798
>>> b = 798
>>> a == b  #相等
True
>>> a is b  #id 不同
False
>>>
>>> a = 798
>>> b = a
>>> a is b  #a、b 指向同一对象,id 相同
True

可结合 《一文理解 Python 中的变量》来理解。


【运算符的优先级】

我们都知道算术中的混合运算可以包含多种运算符,这些运算符可改变运算顺序。

>>> 20 + 4 * 10
60

在这个例子中,同时存在 + 和 * 两种运算符,按照算术规则,先算乘法后算加法,乘法优先级高于加法。

Python 中的每个运算符也都有一定的优先级。优先级高的运算符先被执行,优先级相同的运算符按照从左向右的顺序执行。

我们目前已使用了多种运算符,按优先级从低到高列表如下:

运算符 描述
最低优先级 or Boolean 或
and Boolean 且
not Boolean 非
==、!=、<、<=、>、>=、is、is not 比较运算、id运算
| 按位或
^ 异或
& 按位与
<<、>> 移位操作
+、- 加减
*、/、//、% 乘除、地板除、取模
+x、-x、~x 一元正负、按位取反
最高优先级 ** 幂运算

看一些例子:

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

括号可以改变运算符的优先级,并且有助于理解运算的先后顺序。

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

【扩展的赋值运算符】

赋值(=)运算符用于为变量赋值。既可以为变量赋静态值,也可以为其赋予一个包含其他变量的表达式,并且表达式中还可以包含变量自身。

>>> a = 10
>>> b = 20
>>> c = a * 5 + b
>>> c
70
>>> a = a + 5
>>> a
15
>>> b = b * 3
>>> b
60

对于 a=a+5、b=b*3 这种赋值形式,其含义是在变量自身基础上再进行赋值操作。其前提是,变量必须已经被赋予了初值,否则导致错误。

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

Python 为上边这种赋值方式提供了一个简写形式:

x <op>= y

这种简写等同于:

x = x <op> y

支持这种扩展赋值方式的运算符包括:

+、=、*、/、%、//、**、&、|、^、>>、<<

看一些例子:

>>> a = 5
>>> a += 2
>>> a
7
>>> b = 16
>>> b /= 4
>>> b
4.0

【结语】

本文详细介绍了 Python 提供的各种运算符,以及如何这些运算符来生成表达式。

特别指出了浮点比较运算、短路求值等细节性问题,相信会对你理解和使用运算符和表达式提供帮助。


我们接下来会学一个非常重要的数据类型:字符串。

敬请关注!


欢迎关注本站公众号【python学与思】

python 学与思