4. 控制流与函数
if/for、range、break-continue、for-else、match、函数参数与 *args/**kwargs、对照 JS 的 sleep 与 async/await、LEGB 作用域、lambda 与 PEP 8 速览。
4.1 if 语句
if / elif / else,无switch;多分支用elif比层层嵌套更直。- 模式:把「快速失败 / 守卫子句」写在前面,主体缩进更少。
payload = {"role": "admin", "token": "abc"}
if payload.get("token") is None:
print("缺少 token")
elif payload.get("role") != "admin":
print("权限不足")
else:
print("放行")4.2 for 语句
- 直接迭代可迭代对象;需要
(索引, 值)时用enumerate。 - 不要在遍历集合时修改被遍历对象;字典删键用副本迭代。
items = ["alpha", "beta", "gamma"]
for i, w in enumerate(items, start=1):
print(i, w)
users = {"Hans": "inactive", "robin": "active", "gallahad": "active"}
for name, status in list(users.items()): # list():快照,避免遍历时 resize
if status == "inactive":
del users[name]4.3 range
- 算术序列,左闭右开;惰性,省内存。
- 反模式:
for i in range(len(xs))能用enumerate替换就别手写索引。
xs = ["a", "b", "c"]
for i, x in enumerate(xs):
print(i, x)
# 只有在确实需要索引跳着走时,才用 range
for i in range(0, len(xs), 2):
print(xs[i])4.4 break / continue
break:离开最近一层循环。continue:跳到本层循环的下一次迭代。
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f"{n} = {x} * {n // x}")
break4.5 循环上的 else
else挂在for/while:循环没有被break打断才执行。- 高频坑:把它当成
if的else会读错控制流。
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, "非质数")
break
else:
print(n, "是质数")4.6 pass
语法占位:空类、待实现函数、占位分支。
class Todo(Exception):
pass4.7 match(3.10+,结构化模式)
可做增强分支、解构、守卫条件(if);case _ 兜底。
def http_error(status: int) -> str:
match status:
case 400:
return "请求错误"
case 404:
return "未找到"
case 401 | 403:
return "无权限"
case _:
return "其它"
match {"kind": "user", "id": 99}:
case {"kind": "user", "id": user_id}:
print("user", user_id)
case _:
print("unknown")4.8 定义函数
def;无return等价返回None。- 首段文档字符串 →
__doc__(可被help()、工具链读取)。
def fib(n: int) -> None:
"""打印小于 n 的斐波那契数列"""
a, b = 0, 1
while a < n:
print(a, end=" ")
a, b = b, a + b
print()
def fib2(n: int) -> list[int]:
result: list[int] = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a + b
return result4.9 函数高级用法
默认参数(只算一次)
下面展示经典可变默认值坑:默认形参在函数定义时求值一次。
# 错误示范:默认 list 在定义时只创建一次,所有调用共享
def broken(acc: list[int] = []) -> list[int]:
acc.append(1)
return acc
print(broken()) # [1]
print(broken()) # [1, 1] —— 往往不符合直觉
def ok(acc: list[int] | None = None) -> list[int]:
if acc is None:
acc = []
acc.append(1)
return acc关键字参数与顺序
先位置后关键字:parrot(1000, action="VOOOOOM")。
仅位置 / 仅关键字(3.8+)
/ 前仅限位置;* 后仅限关键字。
def f(pos1: int, /, pos_or_kwd: int, *, kwd_only: int) -> None:
print(pos1, pos_or_kwd, kwd_only)
f(1, 2, kwd_only=3)*args 与 **kwargs
def concat(*parts: str, sep: str = "/") -> str:
return sep.join(parts)
print(concat("a", "b", "c", sep="."))解包调用(对标展开数组 / 对象):
args = [3, 6]
list(range(*args))
d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
def parrot(voltage: str, state: str = "a stiff", action: str = "voom") -> None:
print("-- This parrot wouldn't", action, end=" ")
print("if you put", voltage, "volts through it.", end=" ")
print("E's", state, "!")
parrot(**d)lambda
只适合极短回调;复杂逻辑请写 def(需要栈帧、文档字符串、类型注解时尤其如此)。
pairs = [(1, "one"), (2, "two"), (3, "three"), (4, "four")]
pairs.sort(key=lambda p: p[1])注解不等于校验
def add(a: int, b: int) -> int:
return a + b
print(add("x", "y")) # 'xy' —— 运行期照样执行,除非你用第三方做校验4.10 函数里直接 sleep
- JavaScript:
async/await主要是配合 Promise。 - Python:
time.sleep(seconds)是标准库里的普通同步函数:让当前 OS 线程休眠指定秒数。 - Python 也有
async/await(常见于 asyncio):那是另一套协程 / 事件循环;协程里推迟执行一般用await asyncio.sleep(...),避免在里面长时间time.sleep卡住同一线程上的事件循环
import time
def demo_py() -> None:
time.sleep(0.1) # 同步阻塞当前线程;4.11 作用域
解释器按 LEGB 从内到外找名字:Local(本地)→ Enclosing(外层函数)→ Global(模块全局)→ Built-in(内建)。需要在内层函数里改外层函数的变量时,用 nonlocal;改模块级名字用 global。
命名空间
- 函数:
def/lambda体各自有一层局部作用域。 - 类:类体在执行时也有局部命名空间,结束后名字多半变成类属性(方法会变成描述符等,细节见类章节)。
- 推导式(3.x):列表 / 集合 / 字典推导里的「循环变量」有自己的局部作用域,不会泄漏到外层的函数或模块里。
- 生成器表达式:类似推导式,有自己的作用域。
哪些不是独立作用域
if/elif/else、for/while、try/except/finally、match/case等分支与循环:它们不为赋值语句新建一层作用域;在函数里写的x = 1仍是该函数的局部变量(除非声明了global/nonlocal)。这也是「if里定义的变量,分支外仍可见」的原因。- 模块顶层的缩进块:名字落在模块全局,不是「
if块专属的全局」。
if True:
message = "still module/global or function local, not a new scope"
def demo() -> None:
if True:
a = 1
print(a) # 1 —— a 属于 demo 的局部作用域,不是 if 的子作用域
[x for x in range(3)]
# print(x) # NameError:推导式里的 x 不泄漏(Python 3)4.12 编码风格(PEP 8 摘录)
- 每层缩进 4 空格。
- 一行尽量 ≤ 79(或团队用 88/100,交给 formatter)。
- 二元运算符两侧空格。
- 函数 / 变量:
snake_case;类:CapWords。 - 公共 API 写 docstring。
权威延伸:4. More Control Flow Tools · PEP 8