Python函数是第一类对象,具有__code__、__globals__、__closure__等统一属性,支持装饰器、闭包和高阶函数;参数绑定由inspect.Signature严格匹配;闭包捕获变量引用而非值;functools.wraps可修复装饰器元信息丢失。

Python 函数不是语法糖,是第一类对象(first-class object),这意味着你传给 map() 的可以是函数,赋给变量的可以是函数,放进字典的也可以是函数——只要它有 __call__ 方法。
函数对象本质:为什么 def 和 lambda 都能被 isinstance(..., types.FunctionType) 识别
Python 中所有函数(包括用 def 定义的、lambda 表达式生成的、甚至用 types.FunctionType 动态构造的)在运行时都是 function 类型的实例。它们共享关键属性:
-
__code__:指向底层code object,包含字节码、常量、变量名等 -
__globals__:定义时所在模块的全局命名空间(影响自由变量查找) -
__closure__:仅闭包函数非空,是cell对象元组,保存外部作用域变量 -
__defaults__和__kwdefaults__:分别对应*args默认值和**kwargs关键字默认值
这解释了为何装饰器能通过修改 __code__ 或包装调用逻辑来改变行为,也说明了为何闭包中修改外部变量需用 nonlocal——因为 __closure__ 绑定的是 cell,不是直接引用。
参数绑定机制:从 inspect.signature() 看清 *args、**kwargs 和实际传入的映射关系
函数调用时的参数绑定不是简单“按位置塞”,而是由 CPython 解释器根据 inspect.Signature 执行严格匹配。常见误区:
立即学习“Python免费学习笔记(深入)”;
- 把
*args当成“多余位置参数容器”,其实它是显式声明的元组,未被前面形参捕获的位置参数才进这里 -
**kwargs只接收“未被前面关键字形参匹配”的键值对,且键必须是字符串 - 带
/(positional-only)或*(keyword-only)分隔符的函数,签名结构会直接影响绑定结果
调试建议:遇到 TypeError: func() got multiple values for argument 'x',立刻用 inspect.signature(func) 查看形参分类与默认值,比猜更快。
import inspectdef demo(a, b=10, *args, c, d=20, **kwargs): pass
sig = inspect.signature(demo) print(sig)
(a, b=10, *args, c, d=20, **kwargs)
for name, param in sig.parameters.items(): print(f"{name}: {param.kind} = {param.default if param.default != inspect.Parameter.empty else '—'}")
闭包陷阱:为什么循环中创建的 lambda 全部记住最后一个 i 值
这是因闭包捕获的是变量名(即引用),而非值。循环结束时,i 已固定为终值,所有闭包中的 i 都指向同一内存地址。
- 错误写法:
funcs = [lambda: i for i in range(3)]→ 全部返回2 - 正确解法 1(默认参数快照):
funcs = [lambda i=i: i for i in range(3)] - 正确解法 2(显式闭包函数):
funcs = [(lambda x: lambda: x)(i) for i in range(3)] - 更清晰做法:改用普通函数 + 参数传入,避免隐式闭包
注意:functools.partial 也可用于冻结参数,但它不创建闭包,而是返回新 callable,__closure__ 为空。
高阶函数实战:用 functools.wraps 修复装饰器导致的元信息丢失
不加 @functools.wraps(func) 的装饰器会让被装饰函数的 __name__、__doc__、__annotations__ 全部变成内层 wrapper 的,这对调试、文档生成(如 Sphinx)、类型检查(mypy)全是硬伤。
- 必须在装饰器内部的 wrapper 上加
@functools.wraps(func),而不是在装饰器函数上 -
functools.wraps本质是复制func的__dict__中以__开头的元属性到 wrapper - 若需自定义元信息(如添加
__wrapped__指向原函数),可手动设置,但functools.wraps已覆盖绝大多数场景
没做这一步,help(my_decorated_func) 就只能看到 wrapper 的空文档,而不是你写的函数说明。
函数对象的生命周期、绑定时机、作用域链和元信息完整性,这四点串起来才是真实项目里最常出问题的地方。别只记语法,多用 dir()、inspect.getsource()、dis.dis() 看底层发生了什么。










