函数、参数与作用域
学习目标
- 定义有清晰参数和返回值的函数。
- 区分位置参数、关键字参数、默认参数、可变参数和仅限关键字参数。
- 理解 LEGB 作用域规则,避免可变默认值和隐式全局状态。
核心知识点
函数是一等对象,可以赋值、传参和返回。调用时参数按位置或名称绑定;局部变量遵循 LEGB(Local、Enclosing、Global、Builtins)查找。函数应尽量接收输入、返回结果,把副作用放在边界。
常用用法
def 定义函数与 return
python
def add(left: int, right: int) -> int:
"""返回两个整数之和。"""
return left + right
print(add(2, 3))
# 输出:5没有显式 return 时函数返回 None;返回多个值本质上是返回一个元组。
默认参数
python
def greet(name: str, punctuation: str = "!") -> str:
return f"Hello, {name}{punctuation}"
print(greet("Ada"), greet("Lin", "."))
# 输出:Hello, Ada! Hello, Lin.默认值在函数定义时计算,适合不可变常量;可变对象不要直接做默认值。
关键字参数与位置参数
python
def connect(host: str, port: int, *, timeout: float = 3.0) -> str:
return f"{host}:{port} timeout={timeout}"
print(connect("localhost", 8000, timeout=1.5))
# 输出:localhost:8000 timeout=1.5* 后的参数只能用关键字传递,能让调用更易读并保留扩展空间。
*args 与 **kwargs
python
def summarize(first: int, *numbers: int, unit: str = "个", **metadata: str) -> str:
total = first + sum(numbers)
return f"{total}{unit},标签={metadata.get('tag', 'none')}"
print(summarize(1, 2, 3, unit="项", tag="demo"))
# 输出:6项,标签=demo*args 收集额外位置参数为元组,**kwargs 收集额外关键字参数为字典。
参数解包调用
python
def rectangle(width: int, height: int) -> int:
return width * height
values = (4, 5)
options = {"width": 6, "height": 7}
print(rectangle(*values), rectangle(**options))
# 输出:20 42解包对象的结构必须匹配函数签名,关键字字典的键必须是参数名。
作用域与局部变量
python
count = 10
def read_count() -> int:
count = 20
return count
print(read_count(), count)
# 输出:20 10函数内赋值默认创建局部名字,不会改写同名全局变量。
lambda 与高阶函数
python
records = [("Ada", 3), ("Lin", 1)]
ordered = sorted(records, key=lambda record: record[1])
print(ordered)
# 输出:[('Lin', 1), ('Ada', 3)]lambda 只适合单表达式小函数;需要文档、分支或测试时写 def。
不常用但需要知道
nonlocal
python
def make_counter():
value = 0
def next_value():
nonlocal value
value += 1
return value
return next_value
counter = make_counter()
print(counter(), counter())
# 输出:1 2nonlocal 修改最近的外层函数作用域,常用于闭包状态;共享状态复杂时考虑类。
global
python
status = "idle"
def set_status() -> None:
global status
status = "ready"
set_status()
print(status)
# 输出:ready全局可写状态会增加测试和并发难度,通常用返回值或对象封装替代。
functools.partial
python
from functools import partial
def multiply(left: int, right: int) -> int:
return left * right
double = partial(multiply, 2)
print(double(4))
# 输出:8partial 固定一部分参数,适合把已有函数适配到回调接口。
易混点
- 参数默认值只计算一次,
def f(items=[]): ...会跨调用共享列表。 return a, b等于返回(a, b),调用处可解包。*args是元组,**kwargs是字典,不是任意“多个参数”。- 函数内赋值会使名字成为局部变量,先读取再赋值可能触发
UnboundLocalError。 - 类型提示不负责运行时校验,边界数据仍要显式验证。
课后小问
为什么不推荐用列表作为默认参数? 答案:默认列表在定义函数时只创建一次,多个调用会共享它。 解析:使用
None作为哨兵,在函数体内创建新列表即可避免状态泄漏。*args和**kwargs分别收集什么? 答案:前者收集额外位置参数,后者收集额外关键字参数。 解析:它们也可以在调用时用于解包,但方向相反:收集发生在定义处,解包发生在调用处。
本节小结
先用明确签名写函数,再按需加入关键字专属参数、解包和闭包;让函数少依赖全局状态,代码更容易复用和测试。
快速回顾
- 定义:
def、return - 参数:默认、关键字、
*args、**kwargs - 调用解包:
*values、**options - 作用域:LEGB、
nonlocal、谨慎global - 高阶函数:lambda、
partial
阿源的知识库