Skip to content

01-函数是一等公民

Python 版本要求:Python 3.11+

贯穿项目:Web API 请求处理系统
本节目标:理解函数的四大能力,为装饰器学习打下基础

代码位置decorators_demo/app/decorators/ch01_first_class.py
测试验证uv run pytest tests/test_ch01.py -v


概念铺垫

1. 为什么需要函数进阶?

问题场景:API 路由分发

假设你正在构建一个 Web API,需要根据不同的 URL 路径调用不同的处理函数:

python
# ❌ 方案一:硬编码 if-elif 分支
def handle_request(path: str, data: dict) -> dict:
    if path == "/users":
        return handle_users(data)
    elif path == "/products":
        return handle_products(data)
    elif path == "/orders":
        return handle_orders(data)
    # 每次新增路由都要修改这个函数!违反开闭原则

# ✅ 方案二:函数作为数据结构
routes = {
    "/users": handle_users,
    "/products": handle_products,
    "/orders": handle_orders,
}

def handle_request(path: str, data: dict) -> dict | None:
    handler = routes.get(path)
    if handler is None:
        return None
    return handler(data)  # 直接调用函数变量

核心问题:如何把函数当作普通值来传递、存储、返回?

2. 生活类比:快递分拣中心

把函数想象成快递包裹:

┌─────────────────────────────────────────────────────────────┐
│  快递分拣中心 — 函数是一等公民                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  普通变量 = 普通包裹                                          │
│  • 存放数据(数字、字符串、列表)                              │
│  • 可以赋值、传递、存储                                       │
│                                                             │
│  函数 = 特殊包裹                                             │
│  • 存放"处理逻辑"                                            │
│  • 同样可以赋值、传递、存储、返回                             │
│                                                             │
│  分拣流程:                                                   │
│  ─────────────────                                          │
│  1. 赋值   = 把包裹放到货架上                                  │
│     say_hello = greet                                       │
│                                                             │
│  2. 传参   = 把包裹交给另一个处理站                             │
│     execute(greet, "Alice")                                 │
│                                                             │
│  3. 返回   = 处理站产出新包裹                                  │
│     return get_operation("add")                             │
│                                                             │
│  4. 存储   = 把包裹分类放进储物柜                              │
│     routes = {"/users": handle_users}                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

一句话:

函数是一等公民,可以像数据一样被赋值、传递、存储、返回


L1 理解层:会用

3. 函数是一等公民的四大能力

能力 1:赋值给变量

python
def greet(name: str) -> str:
    return f"Hello, {name}!"

# 函数可以赋值给变量(是引用,不是拷贝)
say_hello: Callable[[str], str] = greet

say_hello("Alice")  # Hello, Alice!
say_hello is greet  # True — 指向同一对象

关键点:赋值创建的是引用,不是副本。say_hellogreet 指向同一个函数对象。

能力 2:作为参数传递

python
def execute(func: Callable[[str], str], value: str) -> str:
    """接收一个函数作为参数并调用它"""
    return func(value)

execute(greet, "World")  # Hello, World!

这是高阶函数的核心特征:接收函数作为参数的函数。

能力 3:作为返回值

python
def get_operation(op: str) -> Callable[[int, int], int]:
    """根据操作名返回对应的计算函数"""
    operations = {
        "add": lambda a, b: a + b,
        "sub": lambda a, b: a - b,
        "mul": lambda a, b: a * b,
    }
    return operations.get(op, lambda _a, _b: 0)

add = get_operation("add")
add(3, 5)  # 8

函数可以动态生成并返回,这是工厂模式和装饰器的基础。

能力 4:存储在数据结构中

python
routes: dict[str, Callable[[dict], dict]] = {
    "/users": handle_users,
    "/products": handle_products,
    "/orders": handle_orders,
}

def dispatch(path: str, req: dict) -> dict | None:
    handler = routes.get(path)
    if handler is None:
        return None
    return handler(req)

这是策略模式路由系统的核心:用数据结构代替条件分支。

4. 贯穿实战:路由分发系统

架构图解

请求:GET /users


┌─────────────┐
│ 路由表      │ routes = {
│ (dict)      │   "/users": handle_users,
│             │   "/products": handle_products,
│             │   "/orders": handle_orders
│             │ }
└─────────────┘


routes["/users"](request)


┌─────────────┐
│ handle_users│ 执行处理函数
│ (函数)      │
└─────────────┘


返回:{"users": ["Alice", "Bob", "Charlie"]}

代码实现

完整代码见 app/decorators/ch01_first_class.py,核心逻辑:

python
# 定义处理函数
def handle_users(_req: dict[str, Any]) -> dict[str, list[str]]:
    return {"users": ["Alice", "Bob", "Charlie"]}

def handle_products(_req: dict[str, Any]) -> dict[str, list[str]]:
    return {"products": ["Apple", "Banana"]}

def handle_orders(_req: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
    return {"orders": [{"id": 1, "user": "Alice"}]}

# 路由表:路径 → 处理函数
routes: dict[str, Callable[[dict[str, Any]], dict[str, Any]]] = {
    "/users": handle_users,
    "/products": handle_products,
    "/orders": handle_orders,
}

# 分发器
def dispatch(path: str, req: dict[str, Any]) -> dict[str, Any] | None:
    handler = routes.get(path)
    if handler is None:
        return None
    return handler(req)

关键代码说明:

代码含义为什么这样写
routes: dict[str, Callable]路径→函数映射函数作为字典值存储
handler = routes.get(path)查找处理函数函数作为变量取出
return handler(req)调用处理函数函数作为变量调用

运行测试验证:

bash
cd decorators_demo
uv run pytest tests/test_ch01.py -v

5. 深入理解:Callable 类型注解

Callable 的含义

python
Callable[[参数类型...], 返回值类型]
  • Callable[[str], str]:接收一个 str 参数,返回 str 的函数
  • Callable[[int, int], int]:接收两个 int 参数,返回 int 的函数
  • Callable[[], None]:无参数,无返回值的函数

常见用法

python
from collections.abc import Callable
from typing import Any

# 变量类型注解
say_hello: Callable[[str], str] = greet

# 函数参数类型注解
def execute(func: Callable[[str], str], value: str) -> str:
    return func(value)

# 函数返回值类型注解
def get_operation(op: str) -> Callable[[int, int], int]:
    ...

# 复杂签名:接收 dict 返回 dict
routes: dict[str, Callable[[dict[str, Any]], dict[str, Any]]] = {...}

注意Callable 在 Python 3.9+ 中也可以从 typing 导入,但 collections.abc.Callable 是更推荐的做法。


L2 实践层:用好

推荐做法

做法原因示例
用函数字典替代 if-elif更简洁、可扩展,符合开闭原则routes = {"/users": handler}
使用 Callable 类型注解明确函数签名,IDE 可提供类型检查routes: dict[str, Callable[[dict], dict]]
collections.abc.Callabletyping.Callable 更推荐(3.9+)from collections.abc import Callable
函数引用不调用时不加括号区分"传递函数"和"调用函数"routes["/users"] = handle_users 而非 handle_users()
lambda 写简单逻辑避免为一次性函数单独命名lambda a, b: a + b

反模式:不要这样做

python
# ❌ 错误:字典存储函数时带括号(会立即调用)
routes = {
    "/users": handle_users(),  # NameError: handle_users 未定义 / 传参缺失
}
# ✅ 正确:传递函数引用,不调用
routes = {
    "/users": handle_users,
}

# ❌ 错误:健忘地修改了所有路由的分发逻辑
def dispatch(path, req):
    if path == "/users":
        return handle_users(req)
    elif path == "/products":
        return handle_products(req)
    # ...每次新增路由都要改函数
# ✅ 正确:用字典路由表
def dispatch(path, req):
    handler = routes.get(path)
    if handler:
        return handler(req)

# ❌ 错误:在循环中定义 lambda 并遗漏参数绑定
multipliers = [lambda x: x * i for i in range(5)]  # 全部返回 x * 4
# ✅ 正确:用默认参数捕获当前值
multipliers = [lambda x, i=i: x * i for i in range(5)]

适用场景

场景是否推荐原因
Web 路由分发✅ 推荐路径→处理函数的字典天然适合路由
回调函数注册✅ 推荐将函数作为参数传递,解耦调用方和实现方
策略模式✅ 推荐用函数字典替代 if-elif 实现多态
简单条件分支(2-3 个)⚠️ 可选少量分支用 if-elif 更直观
需要修改分发逻辑❌ 不推荐函数字典适合静态映射,动态逻辑需要其他方式

L3 专家层:深入

Python 如何实现:函数对象的内部结构

在 CPython 中,def 语句创建的是一个 PyFunctionObject,它包含一组关键内部属性:

┌─────────────────────────────────────────────────────────────┐
│  PyFunctionObject 内部结构                                     │
│                                                             │
│  def greet(name):        定义函数                             │
│      return f"Hello, {name}!"                                │
│                                                             │
│  greet 对象包含:                                              │
│  ┌──────────────────┐                                       │
│  │ __code__         │ → PyCodeObject                         │
│  │  • co_argcount=1 │   (参数个数)                            │
│  │  • co_varnames   │   ("name",)  (局部变量名)              │
│  │  • co_code        │   b't\x00|\x00...'  (字节码指令)      │
│  │  • co_consts     │   ('Hello, ', None)  (常量表)          │
│  │  • co_names      │   ('format',)  (全局名)               │
│  ├──────────────────┤                                       │
│  │ __globals__       │ → {'greet': ..., ...}  (全局命名空间)   │
│  ├──────────────────┤                                       │
│  │ __defaults__      │ → None  (默认参数值)                    │
│  ├──────────────────┤                                       │
│  │ __closure__       │ → None  (闭包 cell 元组,自由函数为 None)│
│  ├──────────────────┤                                       │
│  │ __name__          │ → "greet"                             │
│  ├──────────────────┤                                       │
│  │ __qualname__      │ → "greet"                             │
│  ├──────────────────┤                                       │
│  │ __doc__           │ → None                                │
│  ├──────────────────┤                                       │
│  │ __annotations__   │ → {'name': <class 'str'>, ...}        │
│  └──────────────────┘                                       │
│                                                             │
│  赋值 say_hello = greet 时:                                   │
│  • 不会复制 PyFunctionObject                                     │
│  • say_hello 和 greet 指向同一个对象(引用计数 +1)                │
│  • say_hello is greet → True                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

验证代码

python
def greet(name: str) -> str:
    return f"Hello, {name}!"

say_hello = greet

# 引用语义:指向同一对象
print(say_hello is greet)          # True
print(id(say_hello) == id(greet))  # True

# 内部结构探索
print(greet.__code__)              # <code object greet at ...>
print(greet.__code__.co_argcount)  # 1
print(greet.__code__.co_varnames)  # ('name',)
print(greet.__code__.co_names)     # ('format',) ← 调用了什么全局名
print(greet.__code__.co_consts)    # ('Hello, ', None) ← 常量表
print(greet.__closure__)           # None(自由函数没有闭包)
print(greet.__defaults__)          # None(没有默认参数)

字节码层面:调用 greet("World") 实际执行的是 CALL 指令:

python
import dis

def greet(name: str) -> str:
    return f"Hello, {name}!"

dis.dis(greet)
# 输出:
#   2           0 RESUME                   0
#   3           2 LOAD_CONST               1 ('Hello, ')
#               4 LOAD_FAST                0 (name)
#               6 FORMAT_VALUE             0
#               8 LOAD_CONST               2 (None)
#              10 BUILD_STRING             2
#              12 RETURN_VALUE

调用链:CALL 指令 → 创建新帧(frame)→ 执行字节码 → RETURN_VALUE → 销毁帧。

性能考量

操作时间复杂度说明
func() 调用O(1)通过函数指针直接跳转,开销 ~40ns
routes.get(path) 查找O(1) 平均字典哈希查找
lambda 创建O(1)轻量级函数对象,无状态
大型 if-elif 分发O(n)n 个分支依次比较,性能随分支数下降
方式10 个路由100 个路由1000 个路由
if-elif 分发~50ns~200ns~2000ns
字典分发 routes.get()~40ns~40ns~40ns

结论:字典路由分发在分支数 > 5 时性能优势明显,且代码更简洁。

知识关联

┌─────────────────────────────────────────────────────────────┐
│  知识关联图:函数是一等公民 → 后续知识                               │
│                                                             │
│  第 1 章:函数是一等公民                                        │
│  ┌──────────────────────────────────────┐                   │
│  │ • 函数可赋值、传参、返回、存储        │                   │
│  │ • Callable 类型注解                  │                   │
│  │ • __code__、__closure__ 等内部属性   │                   │
│  └──────────┬───────────────────────────┘                   │
│             │                                                │
│    ┌────────┼────────┬──────────────┐                        │
│    ↓        ↓        ↓              ↓                        │
│  第2章    第3章    第5章           第6章                      │
│  闭包     装饰器   partial/wraps  ParamSpec                  │
│  "函数    "函数     "固定函数       "保留函数                    │
│   内引用   替换"     部分参数"      精确签名"                    │
│   外层"                                                       │
│                                                             │
│  Python 标准库中的"函数是一等公民"用法:                            │
│  ─────────────────────────────────                          │
│  • sorted(data, key=len)     → 函数作为参数传递                │
│  • list(map(str.upper, data))→ 函数作为参数传递                │
│  • signal.signal(SIGINT, handler)→ 函数注册为回调              │
│  • @app.route("/")(view)     → 函数作为装饰器参数              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

延伸知识点

  • 函数是对象type(greet) 返回 <class 'function'>,函数是 function 类的实例
  • __call__ 协议:任何实现了 __call__ 的对象都可以像函数一样调用,这是类装饰器的基础
  • inspect 模块inspect.signature(), inspect.getsource(), inspect.ismethod() 等用于运行时检查函数

6. 自检清单

回答以下问题,检查你是否掌握了核心概念:

  1. 函数赋值给变量后,新变量和原函数是同一个对象吗?
  2. execute(greet, "World") 中,execute 是什么类型的函数?
  3. get_operation("add") 返回的是什么?
  4. 为什么用函数字典代替 if-elif 分支?
  5. Callable[[int, int], int] 表示什么含义?

答案:

  1. 是同一个对象,赋值创建的是引用而非副本(say_hello is greetTrue
  2. 高阶函数 — 接收函数作为参数的函数
  3. 一个函数对象(lambda),接收两个 int 返回 int,具体是加法函数
  4. 更简洁、易扩展、符合开闭原则(新增路由不需要修改分发逻辑)
  5. 接收两个 int 参数,返回 int 的函数类型

7. 本章能力清单

学完本章,你能够:

  • [x] 将函数赋值给变量并理解引用语义
  • [x] 将函数作为参数传递给高阶函数
  • [x] 将函数作为返回值实现函数工厂
  • [x] 用函数字典实现路由分发系统
  • [x] 理解 Callable 类型注解的含义