Python的非程序员教程3
这是一个布尔表达式的小示例(你不需要键入它):
a = 6
b = 7
c = 42
print(1, a == 6)
print(2, a == 7)
print(3, a == 6 and b == 7)
print(4, a == 7 and b == 7)
print(5, not a == 7 and b == 7)
print(6, a == 7 or b == 7)
print(7, a == 7 or b == 6)
print(8, not (a == 7 and b == 6))
print(9, not a == 7 and b == 6)
输出如下:
1 True
2 False
3 True
4 False
5 True
6 True
7 False
8 True
9 False
发生了什么?程序由一系列看起来有点奇怪的 print
语句组成。每个 print
语句打印一个数字和一个表达式。数字用于帮助追踪我正在处理的语句。注意,每个表达式最终的结果要么是 False
,要么是 True
。在 Python 中,false
也可以写成 0
,true
写成 1
。
这些行:
print(1, a == 6)
print(2, a == 7)
分别输出 True
和 False
,正如预期的那样,因为第一个是 True
,第二个是 False
。第三行 print(3, a == 6 and b ==
7)
稍微有些不同。and
操作符意味着,如果前后两个表达式都为真,那么整个表达式为真,否则为假。接下来的行 print(4, a == 7 and b == 7)
展示了如果 and
表达式的部分为假,那么整个表达式为假。and
的行为可以总结如下:
表达式 | 结果 |
---|---|
true and true | true |
true and false | false |
false and true | false |
false and false | false |
注意,如果第一个表达式为假,Python 就不会检查第二个表达式,因为它知道整个表达式已经是假的。你可以试着运行 False and
print("Hi")
,并将其与 True and print("Hi")
做对比。这个技术术语叫做“短路求值”(short-circuit evaluation)。
接下来的行 print(5, not a == 7 and b == 7)
使用了 not
操作符。not
只是将表达式的值取反。这个表达式可以重写为 print(5, a != 7 and b ==
7)
。这里是 not
的真值表:
表达式 | 结果 |
---|---|
not true | false |
not false | true |
接下来的两行,print(6, a == 7 or b == 7)
和 print(7, a == 7 or b
== 6)
使用了 or
操作符。or
操作符如果第一个表达式为真,或者第二个表达式为真,或者两个表达式都为真时返回 true
,否则返回 false
。这里是 or
的真值表:
表达式 | 结果 |
---|---|
true or true | true |
true or false | true |
false or true | true |
false or false | false |
注意,如果第一个表达式为真,Python 就不会检查第二个表达式,因为它知道整个表达式已经为真。这个行为是因为只要 or
表达式的任意一部分为真,整个表达式就为真。第一个部分为真,所以第二部分可以是 false
或 true
,但整个表达式仍然为真。
接下来的两行 print(8, not (a == 7 and b == 6))
和 print(9, not a
== 7 and b == 6)
展示了如何使用括号来分组表达式,并强制先计算一部分。注意,括号改变了表达式的结果,从 false
改为 true
。这是因为括号强制 not
作用于整个表达式,而不是仅仅作用于 a == 7
部分。
这里是一个使用布尔表达式的例子:
list = ["Life", "The Universe", "Everything", "Jack", "Jill", "Life", "Jill"]
# 创建列表的副本,[:] 表示复制整个列表
copy = list[:]
# 对副本进行排序
copy.sort()
prev = copy[0]
del copy[0]
count = 0
# 遍历列表,查找匹配项
while count < len(copy) and copy[count] != prev:
prev = copy[count]
count = count + 1
# 如果没有找到匹配项,那么 count 不能小于 len
# 因为 while 循环会在 count 小于 len 时继续执行,并且没有找到匹配项
if count < len(copy):
print("第一个匹配项:", prev)
输出为:
第一个匹配项: Jill
这个程序通过继续检查 count < len(copy)
和 copy[count]
是否不等于 prev
来寻找匹配项。当 count
大于副本列表的最后一个索引,或者找到匹配项时,and
表达式不再为真,循环退出。if
语句只是用来确保循环退出是因为找到了匹配项。
在这个例子中使用了 and
的另一个“技巧”。如果你查看 and
的真值表,第三行是 false and false
。如果 count >= len(copy)
(也就是 count < len(copy)
为假),那么 copy[count]
就不会被检查。这是因为 Python 知道如果第一个条件为假,那么两个条件不可能同时为真。这就是所谓的短路求值,如果 and
的第二部分可能会在出错时引发异常,短路求值非常有用。我使用第一个表达式 count < len(copy)
来检查 count
是否是一个有效的索引。(如果你不相信我,可以去掉列表中的 "Jill" 和 "Life",检查程序仍然能否正常运行,然后反转 count < len(copy)
和 copy[count]
!= prev
的顺序,改为 copy[count] != prev and count <
len(copy)
。)
布尔表达式可以在你需要同时检查两个或多个不同条件时使用。
布尔运算符说明
对于初学编程的人来说,一个常见的错误是误解布尔运算符的工作方式,这种误解源自 Python 解释器如何读取这些表达式。举例来说,在初学了“and”和“or”语句后,可能会假设表达式 x == ('a' or 'b')
会检查变量 x
是否等于字符串 'a' 或 'b' 中的一个。但事实并非如此。为了理解我在说什么,可以启动一个交互式 Python 会话并输入以下表达式:
>>> 'a' == ('a' or 'b')
>>> 'b' == ('a' or 'b')
>>> 'a' == ('a' and 'b')
>>> 'b' == ('a' and 'b')
你将得到以下出人意料的结果:
>>> 'a' == ('a' or 'b')
True
>>> 'b' == ('a' or 'b')
False
>>> 'a' == ('a' and 'b')
False
>>> 'b' == ('a' and 'b')
True
此时,and
和 or
运算符似乎是坏的。前两条表达式中,'a'
等于 'a' or 'b'
,而 'b'
却不等于它,这看起来毫无道理。更奇怪的是,'b'
等于 'a' and 'b'
也不合常理。通过检查解释器如何处理布尔运算符,我们会发现这些结果确实是你请求的,它们的工作方式与我们想象的有所不同。
当 Python 解释器查看一个 or
表达式时,它会先检查第一个表达式。如果第一个表达式为真,Python 会返回该对象的值而不检查第二个表达式。因为对于一个 or
表达式,如果其中一个值为真,那么整个表达式就为真;因此,程序不需要再检查第二个表达式。另一方面,如果第一个值被评估为假,Python 会检查第二个表达式并返回它的值。第二个表达式决定整个表达式的真假,因为第一个表达式为假。这种“懒惰”的求值方式叫做“短路求值”(short circuiting),在许多编程语言中常见。
类似地,对于 and
表达式,Python 也使用短路技术来加速真值评估。如果第一个表达式为假,那么整个表达式一定为假,Python 会直接返回该值。否则,如果第一个值为真,它会继续检查第二个表达式并返回该值。
值得注意的是,布尔表达式返回的是指示真或假的值,但 Python 对多种对象赋予了真值。要检查任何给定对象 x
的真值,可以使用 bool(x)
函数来查看它的真值。以下是一个包含不同对象真值的表格:
对象 | 真值 |
---|---|
True | True |
1 | True |
非零数值 | True |
非空字符串 | True |
非空列表 | True |
非空字典 | True |
False | False |
0 | False |
字符串 'None' | False |
空字符串 | False |
空列表 | False |
空字典 | False |
现在我们可以理解当我们之前测试那些布尔表达式时得到的困惑结果了。我们来看看解释器如何“看到”这些表达式:
第一个案例:
>>> 'a' == ('a' or 'b') # 首先查看括号内的表达式 "('a' or 'b')"
# 'a' 是一个非空字符串,评估为真
# 返回第一个值:'a'
>>> 'a' == 'a' # 字符串 'a' 等于字符串 'a',表达式为真
True
第二个案例:
>>> 'b' == ('a' or 'b') # 首先查看括号内的表达式 "('a' or 'b')"
# 'a' 是一个非空字符串,评估为真
# 返回第一个值:'a'
>>> 'b' == 'a' # 字符串 'b' 不等于字符串 'a',表达式为假
False
第三个案例:
>>> 'a' == ('a' and 'b') # 首先查看括号内的表达式 "('a' and 'b')"
# 'a' 是一个非空字符串,评估为真,检查第二个值
# 'b' 是一个非空字符串,评估为真
# 返回第二个值作为整个表达式的结果:'b'
>>> 'a' == 'b' # 字符串 'a' 不等于字符串 'b',表达式为假
False
第四个案例:
>>> 'b' == ('a' and 'b') # 首先查看括号内的表达式 "('a' and 'b')"
# 'a' 是一个非空字符串,评估为真,检查第二个值
# 'b' 是一个非空字符串,评估为真
# 返回第二个值作为整个表达式的结果:'b'
>>> 'b' == 'b' # 字符串 'b' 等于字符串 'b',表达式为真
True
所以 Python 实际上是在正确执行它的任务,给出了那些看似荒谬的结果。正如之前提到的,重要的是要认识到布尔表达式在评估时会返回什么值,因为这并不总是显而易见的。
回到最初的表达式,这是你应该如何写它们,以便它们按你想要的方式工作:
>>> 'a' == 'a' or 'a' == 'b'
True
>>> 'b' == 'a' or 'b' == 'b'
True
>>> 'a' == 'a' and 'a' == 'b'
False
>>> 'b' == 'a' and 'b' == 'b'
False
当这些比较被评估时,它们返回的是布尔值(True
或 False
),而不是字符串,因此我们得到了正确的结果。
示例
password1.py
这个程序要求用户输入姓名和密码,然后验证是否允许进入。
name = input("What is your name? ")
password = input("What is the password? ")
if name == "Josh" and password == "Friday":
print("Welcome Josh")
elif name == "Fred" and password == "Rock":
print("Welcome Fred")
else:
print("I don't know you.")
样例运行
What is your name? Josh
What is the password? Friday
Welcome Josh
What is your name? Bill
What is the password? Money
I don't know you.
练习
编写一个程序,让用户猜你的名字,但他们只有 3 次机会,直到程序退出。
解答
print("Try to guess my name!")
count = 1
name = "guilherme"
guess = input("What is my name? ")
while count < 3 and guess.lower() != name: # .lower 允许像 Guilherme 这样的输入仍然匹配
print("You are wrong!")
guess = input("What is my name? ")
count = count + 1
if guess.lower() != name:
print("You are wrong!") # 这个信息在第三次机会时不会打印,所以我们现在打印它
print("You ran out of chances.")
else:
print("Yes! My name is", name + "!")