【Python】4 控制流
4. 更多控制流工具
除了刚介绍的 while 语句,Python 还有一些其它控制流工具。
我们将在本章中遇到它们。
4.1. if 语句
最常见的是 if 语句:
x = 42
# Please enter an integer: 42
if x < 0:
x = 0
print('Negative changed to zero')
elif x == 0:
print('Zero')
elif x == 1:
print('Single')
else:
print('More')
更多说明:
可有零个或多个elif部分,else部分也是可选的。
关键字elif是else if的缩写,用于避免过多缩进。if ... elif ...序列可以作为其它语言中switch/case的替代。
如果把一个值与多个常量比较或检查特定类型/属性,match 语句更有用(详见 4.7)。
4.2. for 语句
Python 的 for 与 C 或 Pascal 不同:
它在序列(如列表、字符串)上按顺序迭代元素:
# 度量一些字符串:
words = ['cat', 'window', 'defenestrate']
for w in words:
print(w, len(w))
修改正在迭代的集合通常不安全。
常见策略是迭代副本或创建新集合:
# 创建示例多项集
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# 策略:迭代一个副本
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
print(users)
# 创建示例多项集
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# 策略:创建一个新多项集
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
print(active_users)
4.3. range() 函数
内置 range() 用于生成等差序列:
for i in range(5):
print(i)
range 不包含终止值;可指定起始、步长(可为负):
print(list(range(5, 10))) # [5, 6, 7, 8, 9]
print(list(range(0, 10, 3))) # [0, 3, 6, 9]
print(list(range(-10, -100, -30))) # [-10, -40, -70]
按索引迭代可结合 range() 与 len():
a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
print(i, a[i])
通常 enumerate() 更方便。
直接打印 range 会显示其表示形式:
print(range(10))
range() 返回的是惰性可迭代对象(不一次性生成完整列表),适合内存敏感场景,也可直接用于像 sum() 这样的函数:
print(sum(range(4))) # 0 + 1 + 2 + 3 => 6
4.4. break 和 continue 语句
break 跳出最近一层 for 或 while 循环:
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f"{n} equals {x} * {n//x}")
break
continue 跳到下一次循环迭代:
for num in range(2, 10):
if num % 2:
print("找到一个奇数",num)
continue
print("找到一个偶数",num)
4.5. 循环的 else 子句
在 for 或 while 循环中,break 可能对应一个 else 子句。
若循环正常结束(未执行 break),则执行 else。
例如查找质数:
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, '=', x, '*', n//x)
break
else:
# 循环到底未找到一个因数
print(n, '是一个质数')
注意:
else属于循环而不是if。else在循环被break、return或异常提前终止时不会执行。
4.6. pass 语句
pass 不执行任何动作,常用于占位:
while True:
pass # 无限等待键盘中断 (Ctrl+C)
用于定义最小类:
class MyEmptyClass:
pass
也可作函数或条件体的占位符:
def initlog(*args):
pass # 记得实现这个!
有时也用省略符 ... 作为占位。
4.7. match 语句
match 将一个表达式与若干 case 模式比较,类似某些语言的 switch,但更像模式匹配(可提取组成部分)。
只有第一个匹配的 case 会执行;
若无匹配则不执行任何分支。
最简单形式是对字面值匹配:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
_ 常用作通配符。
可以用 | 组合字面值:
case 401 | 403 | 404:
return "Not allowed"
支持解包式模式绑定变量:
# point 是一个 (x, y) 元组
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
对于类可以按属性匹配:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
若类定义 __match_args__(如 ('x', 'y')),则位置参数匹配可用,等价于多种写法。
模式可任意嵌套,例如匹配由 Point 组成的列表:
class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
可以为模式添加守卫子句 if,先捕获值再对守卫求值:
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
其它要点摘要:
- 元组/列表模式可匹配任意序列(不能匹配迭代器或字符串)。
- 支持扩展解包,例如
[x, y, *rest]。 - 映射模式例如
{"bandwidth": b, "latency": l}从字典捕获键值,额外键会被忽略。 - 支持
as捕获子模式:case (Point(x1, y1), Point(x2, y2) as p2): ... - 单例对象
True/False/None按 id 比较。 - 可使用具名常量(要用带点号名称以区分捕获变量):
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
color = Color('blue')
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
详见 PEP 636。
4.8. 定义函数
示例:打印小于 n 的斐波那契数列:
def fib(n): # 打印小于 n 的斐波那契数列
"""Print a Fibonacci series less than n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
# 调用
fib(2000)
说明:
- 用
def定义函数,第一条语句为字符串时即为文档字符串(docstring)。 - 函数有自己的局部符号表;
查找变量时遵循局部 -> 外层局部 -> 全局 -> 内置的顺序。 - 函数名是指向函数对象的引用,可以赋给其它名称:
def fib(n): # 打印小于 n 的斐波那契数列
"""Print a Fibonacci series less than n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
f = fib
f(100)
即使没有 return,函数也返回 None。
若希望返回结果列表:
def fib2(n): # 返回斐波那契数组直到 n
"""Return a list containing the Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
f100 = fib2(100)
print(f100)
# f100 -> [0, 1, 1, 2, 3, ... , 89]
4.9. 函数定义详解
函数支持可变数量参数、默认值、关键字参数、仅限位置/仅限关键字等特性。
4.9.1. 默认值参数
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
ask_ok('Do you really want to quit?')
ask_ok('OK to overwrite the file?', 2)
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
默认值在定义处求值(仅计算一次)。
对于可变默认值(如列表),可能导致跨调用共享:
避免方式为使用 None 作为默认并在函数体内创建新对象:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.9.2. 关键字参数
函数可以通过关键字参数调用:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
parrot(1000)
parrot(voltage=1000)
parrot(voltage=1000000, action='VOOOOOM')
parrot('a million', 'bereft of life', 'jump')
parrot('a thousand', state='pushing up the daisies')
注意:
关键字参数必须跟在位置参数之后,且不能重复为同一参数赋值。
使用 **name 接收额外关键字参数(会作为字典传入),可与 *args(接收额外位置参数,产生元组)组合:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
4.9.3. 特殊参数:位置/关键字约束(/ 和 *)
你可以在函数签名中用 / 标记仅限位置参数,用 * 标记仅限关键字参数:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
...
说明:
pos1,pos2仅限位置;pos_or_kwd可以位置或关键字;kwd1,kwd2仅限关键字。
示例:
def standard_arg(arg):
print(arg)
def pos_only_arg(arg, /):
print(arg)
def kwd_only_arg(*, arg):
print(arg)
def combined_example(pos_only, /, standard, *, kwd_only):
print(pos_only, standard, kwd_only)
有关与 **kwds 的交互及使用场景见正文说明。
4.9.4. 任意实参列表(可变位置参数)
使用 *args 收集多余的位置参数为元组:
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
*args 之后的任何形参只能作为仅限关键字参数。
示例:
def concat(*args, sep="/"):
return sep.join(args)
print(concat("earth", "mars", "venus")) # 'earth/mars/venus'
print(concat("earth", "mars", "venus", sep=".")) # 'earth.mars.venus'
4.9.5. 解包实参列表
位置参数可以用 * 从序列解包,用 ** 从字典解包:
print(list(range(3, 6))) # [3, 4, 5]
args = [3, 6]
print(list(range(*args))) # [3, 4, 5]
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)
4.9.6. Lambda 表达式
lambda 创建匿名小函数(仅能是单表达式):
def make_incrementor(n):
return lambda x: x + n
f = make_incrementor(42)
print(f(0)) # 42
print(f(1)) # 43
常用于作为回调或键函数,例如:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
print(pairs)
4.9.7. 文档字符串
文档字符串约定:
- 第一行为简短摘要(首字母大写,句点结尾),
若多行,则第二行为空行以分隔摘要和其余描述。 - 文档处理工具通常会去除多行字符串中的一致缩进(根据第一非空行的缩进来判断)。
示例:
def my_function():
"""什么都不做,只是记录下来。
真的,它没有任何作用。
"""
pass
print(my_function.__doc__)
4.9.8. 函数注解
函数注解为可选的元数据,存放于函数的 __annotations__ 字典,不影响函数执行:
def f(ham: str, eggs: str = 'eggs') -> str:
print("Annotations:", f.__annotations__)
print("Arguments:", ham, eggs)
return ham + ' and ' + eggs
print(f('spam'))
4.10. 小插曲:编码风格
编写更长、更复杂代码时应注意风格。
Python 多数项目遵循 PEP 8。
要点摘要:
- 缩进用
4个空格(不要用制表符)。 - 行长度不超过
79个字符。 - 用空行分隔函数、类及较大代码块。
- 注释最好单独
1行,使用文档字符串。 - 运算符前后、逗号后留空格,但不要在括号内额外空格:
a = f(1, 2) + g(3, 4)。 - 命名:
类用UpperCamelCase,
函数/方法用lowercase_with_underscores;
方法第一个参数通常命名为self。 - 编写面向国际的代码时,优先使用
UTF-8或ASCII;
尽量不要在标识符中使用非ASCII字符。
更多推荐


所有评论(0)