比较与条件技巧
学习如何更高效地使用条件表达式。
本章包含关于 Python 风格和可读性的通用编程技巧。人们普遍认为,如果一个程序不够优美,那它很可能就是错误的(尽管原因可能无关)。Pythonic 程序既优美又更不容易出错。遵循这些技巧,我们可以让代码更加优美。
链式比较操作符
试着回忆一下常见的代数表达式:
在 Python 中,我们可以使用链式比较操作符。
1x, y, z = 5, 10, 12 2x < y < z
我们甚至可以写成:
1x < y <= z != w
这种表达式的求值方式是:先分别计算每个比较操作符的结果,然后将所有结果进行逻辑与(and)运算:
1x < y and y <= z and z != w
我们也可以评估一些奇怪的表达式,比如 x < y > z(检查 是否同时大于 和 ),但这类写法容易引起混淆,应尽量避免。
请自己动手尝试下面的练习!
No configuration found for ID: 5e1220b8866a7b934af17e6e2916ce09c86d989fe8375d645b41b6d033f714d1
使用条件表达式精简代码
条件表达式 x if cond else y 是 if-else 条件语句的替代形式。当条件 cond 为真时,表达式的值为 x;否则为 y。
注意:条件语句本身没有返回值,因为它不是表达式。只有表达式才有值。
以下两个代码片段是等价的:
1# 条件语句 2if cond: 3 value = x 4else: 5 value = y 6 7# 条件表达式 8value = x if cond else y
优点在于,条件表达式更加简洁(一行完成)。不过,条件语句允许在每个分支中包含多行代码,这是条件表达式无法做到的。从某种意义上说,条件表达式与条件语句的关系,就像 lambda 函数与普通函数的关系一样。
在 Python 中,存在一些无名的一行函数,称为匿名函数或 lambda 函数。
条件操作符最适合用于必须使用单个表达式的情况,例如列表推导式中。下面的列表推导式将首字母大写的单词转换为大写(假设单词至少有一个字符):
1WORDS = 'Mary had a little Lamb'.split() 2[(print(word.upper()) if word and word[0].isupper() else print(word)) for word in WORDS ]
注意:如果把条件判断移到列表推导式中,会解决另一个问题——只保留首字母大写的单词并将其转为大写,而其他单词会被过滤掉:
1[print(word.upper()) for word in WORDS if word and word[0].isupper()]
大多数 Python 操作符都是二元操作符(需要两个操作数)。类似地,有些操作符(如
-,+,~,not,await)是一元的,而条件操作符是三元的。
找到缺失的 switch 语句
与 C、C++ 或 Java 不同,Python 没有 switch 语句。该语句允许根据离散变量的值选择执行哪一段代码。例如,下面这段 C 语言代码会打印一些自解释的消息:
1int choice = ...; // 初始化变量 2switch(choice) 3{ 4 case 0: 5 puts("You chose zero."); 6 break; 7 case 1: 8 puts("You chose one."); 9 break; 10 case 99: 11 puts("You chose ninety-nine."); 12 break; 13 default: 14 puts("You chose something else."); 15}
Python 中没有直接对应的语句。我们可以用嵌套的条件语句来替代:
1choice = 0 # 初始化变量 2if choice == 0: 3 print('You chose zero.') 4elif choice == 1: 5 print('You chose one.') 6elif choice == 99: 7 print('You chose ninety-nine.') 8else: 9 print('You chose something else.')
作为额外优势,我们现在可以使用 <=、!= 等比较操作符来描述原始 switch 无法表达的条件。但这会使 switch 和嵌套条件语句不再完全等价。
switch 语句的一个特殊用例是:根据不同的值调用不同的函数。例如,在双人游戏中,若值为 0,则调用 make_computer_move();若值为 1,则调用 make_human_move();其他情况则调用 report_error()。以下是等效的 C 代码:
1switch(choice) { 2 case 0: 3 make_computer_move(); 4 break; 5 case 1: 6 make_human_move(); 7 break; 8 default: 9 report_error(); 10}
将函数转换为字典
我们可以将上述代码翻译为 Python,通过将选项和函数组织成一个字典:
1actions = {0: make_computer_move, 1: make_human_move} 2actions.get(choice, report_error)()
字典中存储的是函数引用,而不是函数调用(注意没有括号)。
函数标识符 是对某个函数的引用,表示该函数已在解释器内存中定义并存在。当我们输入一个已存在的函数名时,解释器会确认其存在,并告诉我们该函数在内存中的地址,但这通常不重要。
dict.get(key, fallback=None) 方法会在字典中查找第一个参数 key。如果找到,就调用对应的函数;否则调用 fallback 函数。我们可以轻松扩展字典以处理任意数量的情况。这个技巧最适用于所有函数具有相同参数数量(包括无参)的情形。如果函数参数不同,我们可以预先准备一组合适的位置参数(可能为空)和一组关键字参数(也可能为空),并将它们传递给选定的函数:
actions.get(choice, report_error)(*positional, **keyword)
让我们用 Python 字典来代替 switch 语句。
No configuration found for ID: ef09362de6d0977f22a36846000452fe20808b6eb5bd8c4dc3c2af8ad33f0818
用 pass 表示空操作
Python 中的一些复合语句(如 class、for、while、if 和 try)要求其主体至少包含一条语句,我们必须提供这样的语句。在 C、C++ 和 Java 中,我们会使用空语句或一对空花括号 {}。Python 不使用花括号(至少在此用途上不使用),而是使用缩进。然而,通过缩进定义的空语句是不可见的:
1class EmptyClass: 2 # 这里有空语句吗?你能看到它吗?
Python 提供了等价于空语句的 pass。当必须使用语句但内容无关时,我们使用 pass:
1class EmptyClass: 2 pass
我们也可以使用字符串字面量,比如“此处不做任何事”,但这违背了“应该有一种明显的方法来做一件事”的原则。
尝试乐观策略
有两种编程方法可用于处理可能失败的操作:乐观 和 悲观。
-
悲观策略主张在执行操作前进行安全检查,以防出现问题。这适用于检查成本远低于异常处理的情况,或者失败频繁且修复代价高昂时。此时,提前检查前置条件更划算。
-
乐观策略认为失败很少发生。与其提前检查,不如直接尝试执行,再通过异常处理机制(
try/except)进行恢复。一个典型的乐观场景是判断字符串是否代表数字。
字符串转换
虽然不简单,但可以用正则表达式描述带指数部分的浮点数字符串格式。然而,更简单的方法是假装字符串代表一个浮点数并尝试转换。如果转换成功,说明它是有效数字;否则不是。
有些算术运算(如无穷大 乘以零、 除以 、 减去 )会产生一种不常见的结果,称为 nan(“非数值”)。nan 可来自 float('nan')、math.nan 或 numpy.nan。
1def string_to_float(s): 2 try: 3 return float(s) 4 except ValueError: 5 return float('nan')
让我们向上述函数传入一个字符串并观察输出。
1print(string_to_float("3.2"))
打开文件
乐观策略的另一个常见应用是打开文件进行读取。我们可以检查文件是否存在、是否为文件(而非目录)、是否属于当前用户、是否可读。但我们可以用一次简单的尝试来替代这一系列检查:如果文件能打开,说明它是可打开的;否则不能。
1try: 2 with open('somefile') as infile: 3 # 对 infile 做些事情 4except (FileNotFoundError, IsADirectoryError, PermissionError): 5 # 做些其他事情
如果预期可能出现其他文件相关错误,可以将它们添加到异常元组中,但应避免使用宽泛的异常捕获。
1try: 2# 你的所有代码(包括 0/0、[][0] 和 'hello'-'world') 3except: 4 exit()
上述代码是一个宽泛异常捕获的例子,应避免使用。我们也可以使用简单的 if 和 elif 条件来处理错误。
选择乐观尝试还是悲观检查,取决于检查的复杂度、尝试的代价以及我们的编程哲学。前两者可以估算,后者则取决于我们接受的教育方式。
关于比较与条件的测验
Quiz
当 x=5,y=5,z=6,w=4 时,表达式 x==y and x!=z and x>w 的求值结果为 True。