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 空格),冒号 : 是语法标志
  • elifelse 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 -= 1

Python 没有 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)才执行

elsefor/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 = (),返回 0

3.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(下一节)。

五、globalnonlocal

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=5

6.3 链式比较

if 0 < x < 100:                # 等价 0 < x and x < 100
if a < b <= c < d:             # 多个也行

七、本节核心打包(7 句话)

  1. if 后跟任何东西,空容器/0/None/""/False 都是假;地道判空用 if not x,判 None 用 if x is None
  2. for-else / while-else 的 else 是 “循环没被 break 才执行” 的意思。
  3. 函数本身是对象,可赋值、传参、作返回值——装饰器和闭包的基础。
  4. 参数顺序:位置 → 默认 → *args → 仅限关键字 → **kwargs;* 后强制关键字调用。
  5. 调用时 *list / **dict 是解包;定义时 *args / **kwargs 是收集。
  6. LEGB:Local → Enclosing → Global → Built-in。函数体里有赋值就把变量当 Local;改 G 用 global,改 E 用 nonlocal
  7. 短路求值实战: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)UnboundLocalErrorglobal total
内层函数 count += 1(无 nonlocal)UnboundLocalErrornonlocal count
data.get("name" or "默认")没默认效果data.get("name") or "默认"
len(items) == 0不 Pythonicnot items
x == None不推荐x is None
export(d, "csv") 但 csv 在 *TypeErrorexport(d, format="csv")