前言
函数是 Python 的乐高积木。参数传递的几种形态、作用域查找规则、闭包——这三块搞明白,写函数式风格的代码就通了。
一、参数的四种形态
def connect(host, port=5432, *, timeout=3, **extra):
print(f"{host}:{port} timeout={timeout} extra={extra}")
# 1. 位置参数:按顺序传
connect("db.local")
# 2. 关键字参数:指名道姓传(推荐, 可读性好)
connect(host="db.local", port=5433)
# 3. 仅关键字参数(* 之后的必须用关键字)
connect("db.local", 5433, timeout=10)
# 4. 可变参数
def add(*args, **kwargs): # args 收集多余位置参数为 tuple, kwargs 收集关键字为 dict
print(args, kwargs)
add(1, 2, 3, debug=True) # (1, 2, 3) {'debug': True}
# 反向解包:调用时把序列/字典摊开
args = ("db.local", 5433)
connect(*args)
⚠️ 默认参数的头号大坑——可变默认值:
def bad(item, lst=[]): # ❌ 默认列表在函数定义时只创建一次!
lst.append(item)
return lst
bad(1) # [1]
bad(2) # [1, 2] —— 惊不惊喜?
# 正确姿势
def good(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
二、作用域:LEGB 规则
Python 查找变量的顺序:Local → Enclosing → Global → Built-in。
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local(L 优先)
inner()
outer()
想在函数内修改外层变量,必须声明:
count = 0
def bump():
global count # 改全局需要 global
count += 1
def make_counter():
n = 0
def counter():
nonlocal n # 改闭包变量需要 nonlocal
n += 1
return n
return counter
良好实践:少用
global——函数应通过参数进、返回值出,保持纯粹。
三、闭包:函数记住它的出生地
闭包 = 内层函数 + 它引用的外层变量。函数返回后,被引用的环境被"记住"了:
def multiplier(factor):
def multiply(x):
return x * factor # 引用了外层的 factor
return multiply
double = multiplier(2)
triple = multiplier(3)
double(10) # 20
triple(10) # 30
print(double.__closure__[0].cell_contents) # 2 —— factor 被闭包持有
闭包的典型应用是工厂函数和装饰器(装饰器本质就是闭包,后面单独写一篇)。
四、lambda 与高阶函数
# lambda: 一个表达式的小函数
square = lambda x: x * x
key_fn = lambda item: item[1]
# 高阶函数: 接收/返回函数的函数
nums = [3, 1, 4, 1, 5, 9, 2, 6]
list(map(lambda x: x * 2, nums)) # [6, 2, 8, ...]
list(filter(lambda x: x > 3, nums)) # [4, 5, 9, 6]
# sorted 的 key 用法(日常最高频)
words = ["banana", "apple", "cherry"]
sorted(words) # 按字母
sorted(words, key=len) # 按长度
sorted(words, key=lambda w: w[::-1]) # 按反转拼写(演示而已)
students = [("zy", 88), ("ab", 95), ("cd", 72)]
sorted(students, key=lambda s: s[1], reverse=True) # 按分数降序
# max/min 也吃 key
max(students, key=lambda s: s[1]) # ('ab', 95)
其实大多数
map/filter场景,推导式可读性更好:
[x * 2 for x in nums]
[x for x in nums if x > 3]
五、实战:一个通用重试函数
把"参数默认值 + 闭包 + 高阶函数"揉在一起:
import time
from functools import wraps
def retry(times=3, delay=1):
"""装饰器工厂: 生成一个重试装饰器"""
def decorator(func):
@wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第{attempt}次失败: {e}")
if attempt == times:
raise # 最后一次失败就抛出
time.sleep(delay)
return wrapper
return decorator
@retry(times=5, delay=2)
def fetch(url):
import urllib.request
return urllib.request.urlopen(url, timeout=3).status
这段代码先用着,原理在《装饰器与生成器》一篇展开。
六、函数是一等公民
# 函数可以存进数据结构、当参数传、当返回值
ops = {"add": lambda a, b: a + b, "sub": lambda a, b: a - b}
ops["add"](3, 4) # 7
def apply(f, data):
return [f(x) for x in data]
apply(str.upper, ["go", "py"]) # ['GO', 'PY']
小结
| 知识点 | 要点 |
|---|---|
| 可变默认参数 | 用 None 哨兵值 |
| 作用域 | LEGB;改外层用 global/nonlocal |
| 闭包 | 内层函数持有外层变量 |
| lambda | 配 sorted/map/filter 的 key 用 |
| 高阶函数 | 函数可以当值传递 |
本文是「Python」系列第 3 篇。