>>>PyPathPython 学习站
首页›基础篇›kp-007
基础篇 · Foundations lv.1 入门 kp-007

函数基础(def、参数、作用域)

前置知识:kp-006

1. 一句话定义

函数用 def 定义,参数系统支持位置/关键字/默认/可变参数四种组合,作用域遵循 LEGB 规则;最大陷阱是可变默认值。

2. 为什么重要

函数是复用的最小单元,参数设计与作用域规则直接决定代码质量。可变默认值陷阱几乎出现在每一份 Python 面试题里,也是真实事故源。

3. 前置知识

kp-003(名字绑定——作用域与传参都是它的延伸)、kp-006。

4. 核心概念

  • 参数五种:位置参数、关键字参数、默认值参数、*args(收集多余位置参数)、**kwargs(收集多余关键字参数)。
  • 仅限位置 / 与仅限关键字 *(3.8+):显式声明调用约定,让 API 稳定。
  • 作用域 LEGB:Local → Enclosing → Global → Builtin,逐层向外查找。
  • 返回:无 return 等价 return None;可返回元组实现"多返回值"。

5. 原理与机制

python
def report(title, *lines, sep=" | ", indent=True):
    """title 为必选位置参数;*lines 收集成元组;sep 是仅限关键字参数"""
    body = sep.join(lines)
    return f"{title}: {body}"

report("日志", "启动", "完成")                       # 日志: 启动 | 完成
report("日志", "a", "b", sep=", ", indent=False)     # 关键字调用

def add(a, b, /, c, *, d=0):     # a,b 仅限位置;d 仅限关键字
    return a + b + c + d

LEGB 与 nonlocal/global:

python
counter = 0
def make():
    n = 0
    def step():
        nonlocal n      # 声明改写外层(闭包话题见 kp-023)
        n += 1
        return n
    return step

可变默认值陷阱与修复:

python
# ✗ 错误:默认列表只创建一次,所有调用共享
def add_item(item, items=[]):
    items.append(item)
    return items

# ✓ 正确:哨兵 None,函数体内新建
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

原理:默认值表达式在 def 执行时求值一次,绑定到函数对象的 __defaults__,之后每次调用复用同一对象(kp-003 的"对象共享"重演)。

6. 关键事实(模型/图示)

text
调用 f(1, x=2):
  位置参数按序填 → 关键字按名填 → 未填的取默认 → 多余的进 *args/**kwargs
  传参 = 名字绑定:形参名绑定到实参对象(不做复制)

类型注解(def f(x: int) -> str:)是可选的、运行时不上锁,见 kp-026。

7. 直观类比

默认值像餐厅的公共小料台:[] 是一盆真实的公共小料,谁来加一勺都动同一盆;正确写法是"每位客人下单时现做一份新的"。

8. 实例与案例

python
def find(*, value, data, key=lambda x: x):    # 强制关键字调用,可读性
    return next((x for x in data if key(x) == value), None)

find(value=2, data=[1, 2, 3])      # 必须写参数名,调用点自文档化

9. 常见误区

  1. 可变默认值(上文)——最高频。
  2. 以为传参会复制对象 —— 只是绑定(kp-003);函数内 append 调用方可见,重新赋值不可见。
  3. 函数内赋值就想改全局 —— 默认是局部;要改全局须 global,改外层须 nonlocal;能不写就不写(可读性)。
  4. 把 lambda 当万能短函数 —— 只能单表达式,复杂逻辑用 def;做排序 key 是它的正当场景。

10. 自测题

  1. def f(a, b=2, *args, c, d=3): ... 中哪些参数必须显式提供?
  2. 如何让一个函数强制调用者写参数名?强制不写参数名呢?
  3. 为什么 def f(x, acc=[]) 危险?给出两种修复方式。
参考答案
  1. a、c(无默认值的位置参数和仅限关键字参数)。
  2. 参数列表里加 * 之后仅限关键字(如 *, c);加 / 之前仅限位置。
  3. 默认值仅求值一次并被共享,跨调用累积;修复:默认 None + 体内新建,或用不可变默认配合显式重建。

11. 与其他知识点的关系

  • kp-023 闭包:嵌套函数与 nonlocal 的完整机制。
  • kp-024 装饰器:函数作为一等公民的应用。
  • kp-026 类型注解:参数与返回值的静态化。

12. 延伸阅读

  • Python 官方教程 §4.7 Defining Functions
  • PEP 570(仅限位置参数)