03- 控制流、函数与作用域
本节目标:掌握 Python 控制流的特色用法、函数定义、丰富的参数机制(位置/关键字/默认/
*args/**kwargs/仅限关键字)、LEGB 作用域查找规则、global/nonlocal,以及一批 Pythonic 惯用写法。掌握后可独立读写 Python 函数。
一、控制流
1.1 if / elif / else
if age < 12:
print("小孩")
elif age < 18:
print("青少年")
else:
print("成人")- 缩进表示代码块(推荐 4 空格),冒号
:是语法标志 elif是else if的缩写- 条件不需要写
( )
1.2 真值测试(Python 特色)
if 后面可跟任何东西,Python 自动判真假。下列都算 False:
False, None, 0, 0.0, "", [], {}, (), set()
其它一切都算 True。所以地道 Python:
if items: # 等价 if len(items) > 0,推荐
if not name: # 等价 if name == "",推荐→ 不要写 if len(x) == 0,地道写法是 if not x。
1.3 for / while
for entity in entities: # 遍历
print(entity)
for i in range(5): # 0,1,2,3,4
print(i)
for i, name in enumerate(names): # 同时拿索引和值
print(i, name)
n = 10
while n > 0:
print(n)
n -= 1Python 没有 C 式 for(i=0;i<n;i++),要计数用 range(n)。
1.4 break / continue
break:跳出当前最内层循环continue:跳过本次,进入下一次
for layer in layers:
if layer == "图框":
continue
if layer == "已损坏":
break
process(layer)1.5 ⭐ for-else / while-else(Python 独有)
for x in items:
if found(x):
break
else:
print("没找到") # ← 循环正常结束(没 break)才执行
else配for/while,意思是”循环完整结束、没被 break”才执行。
1.6 三元表达式
status = "成人" if age >= 18 else "未成年"格式:值 if 条件 else 另一个值。条件在中间。
1.7 match-case(Python 3.10+)
match entity_type:
case "LINE":
handle_line()
case "INSERT" | "ATTRIB": # 多模式
handle_insert()
case _: # 默认
handle_unknown()_ 通配符,| 是”或”。
二、函数定义与”函数是对象”
2.1 基本语法
def add(a, b):
"""两数相加(这是 docstring)"""
return a + b
x = add(3, 5)def 函数名(参数):开头,函数体缩进return返回值;没 return 默认返回None"""..."""是 docstring,用help(add)查看
2.2 多返回值 = 返回 tuple
def get_size(entity):
return 10, 20 # 返回 tuple (10, 20)
w, h = get_size(e) # 拆包2.3 ⭐ 函数本身是对象(first-class)
函数和数字、字符串一样是个”值”,可以:
赋值给变量:
def greet(name):
print("hi", name)
f = greet
f("张三") # 等价 greet("张三")作为参数传给另一函数:
def apply(func, value):
return func(value)
apply(lambda x: x * 2, 5) # 10作为返回值:
def make_adder(n):
def adder(x):
return x + n
return adder
add5 = make_adder(5)
add5(10) # 15→ 这是装饰器、高阶函数、闭包的基础。先记住”函数也是值”。
三、参数机制(本节重点 ⭐)
3.1 位置参数 vs 关键字参数
def greet(name, greeting):
print(greeting, name)
greet("张三", "你好") # 位置
greet(name="张三", greeting="你好") # 关键字
greet(greeting="你好", name="张三") # 关键字可换序→ 关键字调用让代码自说明,推荐重要参数明写名字。
3.2 默认参数
def greet(name, greeting="你好"):
print(greeting, name)
greet("张三") # 你好 张三
greet("李四", "Hi") # Hi 李四⚠️ 默认值不能用可变对象(list/dict/set)!回顾第 02 节:
# 错
def f(lst=[]): ...
# 对
def f(lst=None):
if lst is None:
lst = []3.3 *args —— 不定数量的位置参数
* 加变量名,打包成 tuple:
def sum_all(*args):
return sum(args)
sum_all(1, 2, 3) # args = (1, 2, 3),返回 6
sum_all() # args = (),返回 03.4 **kwargs —— 不定数量的关键字参数
** 加变量名,打包成 dict:
def print_info(**kwargs):
for k, v in kwargs.items():
print(k, "=", v)
print_info(name="张三", age=30, city="北京")
# kwargs = {'name': '张三', 'age': 30, 'city': '北京'}3.5 参数完整顺序(必背)
def func(pos1, pos2, # 1. 位置参数
default1=10, # 2. 带默认值的参数
*args, # 3. 收集多余位置参数
kw_only=20, # 4. 仅限关键字参数
**kwargs): # 5. 收集多余关键字参数
...常见组合:
def f(a, b): ... # 只位置
def f(a, b=10): ... # 位置+默认
def f(a, *args): ... # 位置+不定位置
def f(a, b=10, **kwargs): ... # 位置+默认+不定关键字
def f(*args, **kwargs): ... # 万能接收(装饰器常用)3.6 仅限关键字参数(* 后必须用关键字)
def export(data, *, format="csv"): # * 后的 format 必须用关键字
print(format)
export(my_data, format="json") # ✅
export(my_data, "json") # ❌ 报错→ 工程级库(pandas、ezdxf)大量用这种机制提升可读性。
3.7 仅限位置参数(/ 前必须按位置,Python 3.8+)
def divide(a, b, /): # / 前必须按位置
return a / b
divide(10, 2) # ✅
divide(a=10, b=2) # ❌3.8 调用时解包 * 和 **
args = ("张三", "你好")
greet(*args) # 等价 greet("张三", "你好")
kwargs = {"name": "李四", "greeting": "Hi"}
greet(**kwargs) # 等价 greet(name="李四", greeting="Hi")→ ezdxf 源码常见 func(*pos, **kw) 这种”原样转发”模式。
3.9 参数分配的规则
调用 f("张三", 1, 2, age=30) 给 def f(name, *args, **kwargs):
name ← 第 1 个位置参数 "张三"
args ← 剩下的位置参数 (1, 2)
kwargs ← 所有关键字参数 {'age': 30}
位置参数从左到右先填普通参数 → 剩下进
*args;关键字参数先填同名命名参数 → 剩下进**kwargs。
四、作用域:LEGB 规则
Python 查找一个名字时,按固定 4 层顺序从内向外:
L Local 本函数内部定义的变量
E Enclosing 外层(嵌套)函数的变量
G Global 当前模块(文件)顶层定义的变量
B Built-in Python 内置(print, len, range...)
查找演示
x = "全局 x" # G
def outer():
x = "outer 的 x" # E (对 inner)
def inner():
x = "inner 的 x" # L
print(x) # 找 x:L 有 → "inner 的 x"
inner()逐层去掉变量,会依次落到 E、G、B。
图示
┌─────────────────────────────────┐
│ B - Built-in (print/len/range) │
│ ┌─────────────────────────────┐│
│ │ G - Global (本文件顶层) ││
│ │ ┌──────────────────────┐ ││
│ │ │ E - Enclosing │ ││
│ │ │ ┌──────────────┐ │ ││
│ │ │ │ L - Local │ │ ││ ← 从这开始,向外找
│ │ │ └──────────────┘ │ ││
│ │ └──────────────────────┘ ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘
⚠️ 关键陷阱:函数内”赋值”会强制把变量标 Local
x = 10
def f():
print(x) # UnboundLocalError!
x = 20 # 这一行让 x 被标 Local
f()Python 在执行函数前扫描函数体,看到任何 x = ... 就把 x 标 Local。然后 print(x) 找 L 里的 x 但还没赋值 → 报错。
只要函数体里出现
x = ...,那 x 在整个函数里就是 Local 的。
要修改外层变量需要 global / nonlocal(下一节)。
五、global 与 nonlocal
global —— 在函数内改全局变量
count = 0
def increment():
global count # ← 声明:这个 count 是全局的
count += 1
increment()
increment()
print(count) # 2不写 global 会因上面的陷阱报 UnboundLocalError。
nonlocal —— 在内层函数改外层函数变量(E)
def outer():
count = 0
def inner():
nonlocal count # ← 声明:count 是外层函数的
count += 1
inner(); inner(); inner()
print(count) # 3不写 nonlocal,inner 会试图新建 Local count → 报错。
口诀:改 G 的用
global,改 E 的用nonlocal。函数里光”先定义”没用——那只是新建 Local。
工程经验
global / nonlocal 都尽量少用——让代码难追踪。需要跨函数共享状态:
- 优先用类封装(下一模块讲)
- 函数返回多值
- 真要全局配置 → 专门的配置模块或对象
六、Pythonic 风格
6.1 短路求值
and:左边为假就停,返回左边;否则返回右边。
or:左边为真就停,返回左边;否则返回右边。
print(0 and 5) # 0
print(3 and 5) # 5
print(0 or 5) # 5
print(3 or 5) # 3实战:给可能是 None 的变量设默认值:
username = data.get("name") or "匿名"执行顺序:先算 data.get("name") 拿到值 → 若值是假(None/"" 等)就用 “匿名”。
对比三种写法的区别:
| 写法 | 效果 |
|---|---|
data.get("name" or "匿名") | ❌ 永远查 key=“name”,无默认 |
data.get("name") or "匿名" | ✅ 值为 None / 空串时回退 “匿名” |
data.get("name", "匿名") | 仅在 key 不存在时用默认;key 存在但值是 None / "" 不回退 |
→ 最稳的”无值兜底”写法是 ... or "默认"。
6.2 常见惯用法
# 判 None
if x is None: # ✅ 推荐
if x == None: # ❌
# 判空
if not items: # ✅ Pythonic
if len(items) == 0: # ⚠️ 啰嗦
# dict 默认值
name = config.get("name", "默认名") # get 第二参就是默认
# 交换变量(一行)
a, b = b, a
# 多变量赋值
x, y, z = 1, 2, 3
# 拆包
first, *rest = [1, 2, 3, 4, 5] # first=1, rest=[2,3,4,5]
*init, last = [1, 2, 3, 4, 5] # init=[1,2,3,4], last=56.3 链式比较
if 0 < x < 100: # 等价 0 < x and x < 100
if a < b <= c < d: # 多个也行七、本节核心打包(7 句话)
if后跟任何东西,空容器/0/None/""/False 都是假;地道判空用if not x,判 None 用if x is None。for-else/while-else的 else 是 “循环没被 break 才执行” 的意思。- 函数本身是对象,可赋值、传参、作返回值——装饰器和闭包的基础。
- 参数顺序:位置 → 默认 →
*args→ 仅限关键字 →**kwargs;*后强制关键字调用。 - 调用时
*list/**dict是解包;定义时*args/**kwargs是收集。 - LEGB:Local → Enclosing → Global → Built-in。函数体里有赋值就把变量当 Local;改 G 用
global,改 E 用nonlocal。 - 短路求值实战:
val or 默认;但要区分”无值”和”值为空串”用is None显式判。
八、关键术语速查表
| 术语 | 含义 |
|---|---|
| 真值测试 | Python 自动把对象转布尔的规则 |
| docstring | 函数体首行的三引号说明文档 |
| first-class function | 函数是对象,可赋值传参返回 |
| 位置参数 | 按位置对号入座 |
| 关键字参数 | 调用时明写 name=value |
| 默认参数 | 定义时给默认值 |
*args | 收集多余位置参数为 tuple |
**kwargs | 收集多余关键字参数为 dict |
| 仅限关键字参数 | * 之后,必须用 keyword 传 |
| 仅限位置参数 | / 之前,必须按位置传 |
解包 * / ** | 调用时把 list/dict 展开成参数 |
| LEGB | 名字查找顺序:Local→Enclosing→Global→Built-in |
global | 函数内声明”用全局变量” |
nonlocal | 内层函数声明”用外层函数变量” |
| 短路求值 | and/or 看到结果就停,返回相应值 |
九、常见陷阱速查表
| 写法 | 问题 | 修复 |
|---|---|---|
def f(lst=[]) | 默认参数对象跨调用共享 | def f(lst=None): if lst is None: lst=[] |
函数里 total += x(无 global) | UnboundLocalError | 加 global total |
内层函数 count += 1(无 nonlocal) | UnboundLocalError | 加 nonlocal count |
data.get("name" or "默认") | 没默认效果 | data.get("name") or "默认" |
len(items) == 0 | 不 Pythonic | not items |
x == None | 不推荐 | x is None |
export(d, "csv") 但 csv 在 * 后 | TypeError | export(d, format="csv") |