Python 怎么获取函数的名字、参数、签名?函数有哪些内省属性?
简化版
**Python 的函数是「一等对象」,自带一堆「内省属性」让你在运行时查看它的名字、文档、参数、默认值等;获取「参数签名」的最佳工具是 inspect.signature()。**常用的函数属性:① f.__name__(函数名)、② f.__doc__(文档字符串)、③ f.__defaults__(位置参数的默认值元组)、④ f.__kwdefaults__(仅关键字参数的默认值)、⑤ f.__annotations__(类型注解字典)、⑥ f.__module__(所在模块)、⑦ f.__code__(代码对象,含 co_varnames 参数名、co_argcount 参数个数等)、⑧ f.__closure__(闭包变量)、⑨ f.__dict__(函数也能挂自定义属性)。最实用的 inspect 模块:inspect.signature(f) 返回一个 Signature 对象,能优雅地拿到所有参数、默认值、类型注解、参数种类(位置/关键字/可变),还能 sig.bind(args) 校验参数、inspect.getsource(f) 拿源码。用途:装饰器(保留/检查签名)、框架(依赖注入、自动生成 API 文档/CLI)、调试、序列化。核心记忆:函数内省属性 __name__/__doc__/__defaults__/__annotations__/__code__,查参数签名用 inspect.signature()(最优雅);functools.wraps 靠这些属性保留原函数元数据。
详细版
函数的内省属性:
| 属性/方法 | 内容 |
|---|---|
f.__name__ | 函数名 |
f.__doc__ | 文档字符串 |
f.__defaults__ | 位置参数默认值元组 |
f.__kwdefaults__ | 仅关键字参数默认值字典 |
f.__annotations__ | 类型注解字典 |
f.__module__ | 所属模块名 |
f.__code__.co_varnames | 局部变量名(含参数) |
f.__code__.co_argcount | 位置参数个数 |
inspect.signature(f) | 完整签名对象(推荐) |
import inspect
def greet(name: str, greeting: str = "Hello", *args, punct: str = "!", **kwargs) -> str:
"""打招呼函数"""
return f"{greeting}, {name}{punct}"
# 基本属性
print(greet.__name__) # 'greet'
print(greet.__doc__) # '打招呼函数'
print(greet.__defaults__) # ('Hello',) —— 位置参数的默认值
print(greet.__kwdefaults__) # {'punct': '!'} —— 仅关键字参数默认值
print(greet.__annotations__) # {'name': str, 'greeting': str, 'return': str}
print(greet.__module__) # '__main__'
# code 对象
print(greet.__code__.co_argcount) # 2(name, greeting)
print(greet.__code__.co_varnames) # ('name', 'greeting', 'args', 'punct', ...)
# inspect.signature(最推荐,优雅统一)
sig = inspect.signature(greet)
print(sig) # (name: str, greeting: str = 'Hello', *args, punct: str = '!', **kwargs) -> str
for name, param in sig.parameters.items():
print(name, param.kind, param.default)
# name POSITIONAL_OR_KEYWORD <empty>
# greeting POSITIONAL_OR_KEYWORD Hello
# args VAR_POSITIONAL <empty>
# punct KEYWORD_ONLY !
# kwargs VAR_KEYWORD <empty>
# bind:校验/绑定参数
bound = sig.bind("Tom", greeting="Hi")
print(bound.arguments) # {'name': 'Tom', 'greeting': 'Hi'}
# 函数可挂自定义属性(函数也是对象)
greet.call_count = 0
greet.call_count += 1
# 拿源码
# print(inspect.getsource(greet))
# functools.wraps 就是靠这些属性保留原函数元数据
from functools import wraps
def deco(f):
@wraps(f) # 拷贝 __name__/__doc__/__wrapped__ 等
def wrapper(*a, **k): return f(*a, **k)
return wrapper
⚠️ 核心认知:Python 的函数是「一等对象」——它不只是可调用的代码,还是一个带有丰富元数据(属性)的对象,你可以在运行时读取它的名字、文档、参数、默认值、类型注解,甚至给它挂自定义属性。这些内省能力是很多「魔法」的基础:
functools.wraps装饰器就是把原函数的__name__/__doc__/__module__/__dict__/__wrapped__拷贝到 wrapper 上(否则装饰后函数名会变成wrapper、文档丢失);FastAPI/Click 这类框架靠inspect.signature+__annotations__读取参数和类型来自动做「依赖注入、请求参数解析、生成 API 文档/CLI」;pytest 靠内省 fixture 参数名做依赖注入。实践上强烈推荐用inspect.signature()而不是直接读__defaults__/__code__——因为直接读这些底层属性要自己处理位置参数、关键字参数、可变参数、默认值的对应关系(很繁琐易错),而signature把这些统一成优雅的Parameter对象(有.name、.kind、.default、.annotation),还能bind校验参数;signature也能正确处理被functools.wraps包装过的函数(跟随__wrapped__找到原函数签名)。
完整版教学
一、函数是一等对象
先理解函数作为对象的本质:
Python 函数是"一等对象(first-class object)":
函数不只是"可调用的代码",还是一个"对象"
→ 有属性、能赋值、能传递、能挂自定义属性
作为对象的表现:
① 能赋值给变量:g = greet
② 能作参数传递:map(greet, names)
③ 能作返回值:return greet
④ 能存进容器:funcs = [greet, ...]
⑤ 有属性(内省):greet.__name__、greet.__doc__
⑥ 能挂自定义属性:greet.count = 0
内省(introspection):
运行时"检查对象自身"的能力
→ 读函数的名字、参数、文档、注解等元数据
为什么函数需要这些属性:
① 调试/日志:知道调用的是哪个函数
② 装饰器:保留/检查原函数信息
③ 框架:读参数/注解做自动化(依赖注入、文档生成)
④ 文档工具:提取函数签名和文档
函数对象 vs 调用:
greet # 函数对象本身(可内省、传递)
greet() # 调用函数(执行)
所以函数是一等对象:有属性/能传递/可挂自定义属性;内省=运行时检查函数元数据
Python 函数是「一等对象(first-class object)」:函数不只是可调用的代码、还是一个对象(有属性、能赋值、能传递、能挂自定义属性)。作为对象的表现:① 能赋值给变量、② 能作参数传递、③ 能作返回值、④ 能存进容器、⑤ 有属性(内省)、⑥ 能挂自定义属性。内省(introspection):运行时「检查对象自身」的能力(读函数的名字、参数、文档、注解等元数据)。为什么函数需要这些属性:① 调试/日志、② 装饰器(保留/检查原函数信息)、③ 框架(读参数/注解做自动化)、④ 文档工具。函数对象 vs 调用:greet(函数对象本身、可内省传递)、greet()(调用函数、执行)。理解「函数是一等对象:有属性/能赋值传递返回/存容器/挂自定义属性;内省=运行时检查函数元数据;用途调试/装饰器/框架自动化/文档;greet 是对象、greet()是调用」,就理解了函数作为对象。
二、常用内省属性
理解各个内省属性:
常用函数属性:
__name__:函数名(字符串)
greet.__name__ → 'greet'
用途:日志、调试、装饰器保留名字
__doc__:文档字符串
greet.__doc__ → '打招呼函数'
用途:help()、文档提取
__defaults__:位置参数的默认值(元组)
def f(a, b=1, c=2): ...
f.__defaults__ → (1, 2) # 从右往左对应有默认值的参数
__kwdefaults__:仅关键字参数的默认值(字典)
def f(*, x=1, y=2): ...
f.__kwdefaults__ → {'x': 1, 'y': 2}
__annotations__:类型注解(字典)
def f(a: int, b: str = "x") -> bool: ...
f.__annotations__ → {'a': int, 'b': str, 'return': bool}
用途:类型检查、框架读类型(FastAPI/pydantic)
__module__:所属模块名
greet.__module__ → '__main__' 或 'mypackage.mymodule'
__qualname__:限定名(含类名,如 'MyClass.method')
__dict__:函数的自定义属性字典
greet.tag = "important"
greet.__dict__ → {'tag': 'important'}
→ 函数能像对象一样挂属性
__wrapped__:被 functools.wraps 包装时指向原函数
所以属性:__name__名/__doc__文档/__defaults__默认值/__annotations__注解/__module__模块
常用函数属性:__name__(函数名字符串、日志/调试/装饰器保留名字);__doc__(文档字符串、help()/文档提取);__defaults__(位置参数默认值元组,def f(a, b=1, c=2) → (1, 2)、从右往左对应有默认值的参数);__kwdefaults__(仅关键字参数默认值字典,def f(*, x=1, y=2) → {'x':1, 'y':2});__annotations__(类型注解字典,def f(a: int) -> bool → {'a': int, 'return': bool}、框架读类型 FastAPI/pydantic);__module__(所属模块名);__qualname__(限定名含类名如 MyClass.method);__dict__(函数的自定义属性字典、greet.tag = "x");__wrapped__(被 functools.wraps 包装时指向原函数)。理解「属性:__name__名/__doc__文档/__defaults__位置默认值(元组从右往左)/__kwdefaults__关键字默认值/__annotations__注解(字典含 return)/__module__模块/__qualname__限定名/__dict__自定义属性/__wrapped__原函数」,就掌握了内省属性。
三、code 代码对象
理解代码对象的内省信息:
__code__:函数的"代码对象"(编译后的字节码等)
包含函数的底层信息
常用 co_ 属性:
f.__code__.co_name # 函数名
f.__code__.co_argcount # 位置参数个数(不含 *args/**kwargs)
f.__code__.co_posonlyargcount # 仅位置参数个数(3.8+)
f.__code__.co_kwonlyargcount # 仅关键字参数个数
f.__code__.co_varnames # 局部变量名元组(前面是参数)
f.__code__.co_filename # 定义所在文件
f.__code__.co_firstlineno # 定义所在行号
f.__code__.co_consts # 常量
f.__code__.co_names # 引用的全局名
例子:
def f(a, b, c=1, *args, d, **kwargs):
x = 1
return a
f.__code__.co_argcount → 3(a, b, c)
f.__code__.co_kwonlyargcount → 1(d)
f.__code__.co_varnames → ('a','b','c','args','d','kwargs','x')
为什么用 __code__(较底层):
① 拿参数名(co_varnames 前 co_argcount 个)
② 拿定义位置(文件、行号)——错误定位
③ 底层分析(字节码工具、profiler)
但一般用 inspect 更好:
直接读 __code__ 要自己算参数对应关系(繁琐)
→ inspect.signature 封装得更优雅
所以__code__含底层信息(co_argcount 参数数/co_varnames 变量名/co_filename 文件),但用 inspect 更优雅
__code__:函数的「代码对象」(编译后的字节码等、包含底层信息)。常用 co_ 属性:co_name(函数名)、co_argcount(位置参数个数、不含 *args/**kwargs)、co_posonlyargcount(仅位置参数个数、3.8+)、co_kwonlyargcount(仅关键字参数个数)、co_varnames(局部变量名元组、前面是参数)、co_filename(定义所在文件)、co_firstlineno(定义所在行号)。例子:def f(a, b, c=1, *args, d, **kwargs)——co_argcount → 3、co_kwonlyargcount → 1、co_varnames → ('a','b','c','args','d','kwargs','x')。为什么用 __code__(较底层):① 拿参数名、② 拿定义位置(错误定位)、③ 底层分析(字节码工具、profiler)。但一般用 inspect 更好:直接读 __code__ 要自己算参数对应关系(繁琐)、inspect.signature 封装更优雅。理解「__code__含底层信息:co_argcount 位置参数数/co_kwonlyargcount 关键字参数数/co_varnames 变量名(前面是参数)/co_filename 文件/co_firstlineno 行号;用于拿参数名/定位/底层分析;但用 inspect 更优雅」,就掌握了代码对象。
四、inspect.signature(推荐)
理解最优雅的签名内省工具:
inspect.signature(f):获取函数签名(最推荐)
返回 Signature 对象,优雅统一地表示所有参数
import inspect
sig = inspect.signature(greet)
print(sig) # (name: str, greeting: str = 'Hello', ...) -> str
遍历参数:
for name, param in sig.parameters.items():
param.name # 参数名
param.kind # 参数种类
param.default # 默认值(无则 Parameter.empty)
param.annotation # 类型注解(无则 Parameter.empty)
参数种类(param.kind):
POSITIONAL_ONLY # 仅位置(/ 前)
POSITIONAL_OR_KEYWORD # 普通(可位置可关键字)
VAR_POSITIONAL # *args
KEYWORD_ONLY # 仅关键字(* 后)
VAR_KEYWORD # **kwargs
bind:绑定/校验参数
bound = sig.bind("Tom", greeting="Hi")
bound.arguments # {'name':'Tom', 'greeting':'Hi'}
bound.apply_defaults() # 填入默认值
→ 校验传参是否合法(不匹配抛 TypeError)
→ 框架用它做参数解析
返回值注解:
sig.return_annotation # str(-> str)
为什么 signature 比直接读属性好:
① 统一处理所有参数种类和默认值(不用自己算)
② 跟随 __wrapped__(正确处理被 wraps 装饰的函数)
③ 优雅的 API(Parameter 对象)
④ bind 校验参数
所以 inspect.signature 最优雅:统一参数种类/默认值/注解,能 bind 校验,跟随__wrapped__
inspect.signature(f):获取函数签名(最推荐)——返回 Signature 对象、优雅统一地表示所有参数。遍历参数:for name, param in sig.parameters.items()——param.name(参数名)、param.kind(参数种类)、param.default(默认值、无则 Parameter.empty)、param.annotation(类型注解)。参数种类(param.kind):POSITIONAL_ONLY(仅位置、/ 前)、POSITIONAL_OR_KEYWORD(普通)、VAR_POSITIONAL(*args)、KEYWORD_ONLY(仅关键字、* 后)、VAR_KEYWORD(**kwargs)。bind:绑定/校验参数:sig.bind("Tom", greeting="Hi") → bound.arguments、bound.apply_defaults() 填默认值(校验传参合法、不匹配抛 TypeError、框架用它做参数解析)。返回值注解:sig.return_annotation。为什么 signature 比直接读属性好:① 统一处理所有参数种类和默认值、② 跟随 __wrapped__(正确处理被 wraps 装饰的函数)、③ 优雅的 API、④ bind 校验参数。理解「inspect.signature 最优雅:返回 Signature、parameters 遍历参数(name/kind/default/annotation);kind 五种(仅位置/普通/*args/仅关键字/**kwargs);bind 校验绑定参数;比直接读属性好(统一处理+跟随__wrapped__+bind)」,就掌握了 signature。
五、inspect 的其他工具与应用
理解 inspect 的更多能力和实际应用:
inspect 的其他常用工具:
inspect.getsource(f) # 拿函数源码(字符串)
inspect.getsourcefile(f) # 源文件路径
inspect.getdoc(f) # 文档(清理缩进后)
inspect.getmembers(obj) # 所有成员(属性+方法)
inspect.isfunction(x) # 是否函数
inspect.ismethod(x) # 是否方法
inspect.isgeneratorfunction(x) # 是否生成器函数
inspect.iscoroutinefunction(x) # 是否 async 函数
inspect.getfullargspec(f) # 老式参数信息(signature 更推荐)
inspect.stack() # 当前调用栈
实际应用:
① functools.wraps 保留元数据(装饰器必备):
@wraps(f) # 拷贝 __name__/__doc__/__module__/__wrapped__ 等
def wrapper(...): ...
→ 不加 wraps,装饰后 __name__ 变 'wrapper'、文档丢失
② 框架依赖注入(FastAPI/pytest):
读 signature 的参数名/注解 → 自动注入对应的依赖/请求参数
③ 自动生成 CLI/API 文档(Click/FastAPI):
读参数、类型注解、默认值 → 生成命令行/接口文档
④ 参数校验:
sig.bind(args) 校验传参是否匹配签名
⑤ 调试/日志:
f.__name__、定义位置、调用栈
内省的注意:
① 被装饰的函数:用 wraps 才能正确内省(signature 跟随 __wrapped__)
② 内置函数:部分没有完整签名信息(C 实现)
③ 性能:内省有开销,别在热路径频繁调用
所以 inspect 还有 getsource/isfunction 等;应用装饰器 wraps/框架注入/文档生成/校验
inspect 的其他常用工具:getsource(f)(拿源码)、getsourcefile(f)(源文件路径)、getdoc(f)(清理缩进的文档)、getmembers(obj)(所有成员)、isfunction/ismethod/isgeneratorfunction/iscoroutinefunction(类型判断)、getfullargspec(f)(老式参数信息)、stack()(当前调用栈)。实际应用:① functools.wraps 保留元数据(装饰器必备)(拷贝 __name__/__doc__/__wrapped__、不加 wraps 装饰后 __name__ 变 ‘wrapper’、文档丢失)、② 框架依赖注入(FastAPI/pytest)(读 signature 的参数名/注解自动注入)、③ 自动生成 CLI/API 文档、④ 参数校验(sig.bind)、⑤ 调试/日志。内省的注意:① 被装饰的函数用 wraps 才能正确内省、② 内置函数部分没有完整签名(C 实现)、③ 性能有开销别在热路径频繁调用。理解「inspect 还有 getsource/isfunction/iscoroutinefunction/stack;应用 functools.wraps 保留元数据(装饰器必备)、框架注入(读 signature)、文档生成、参数校验 bind、调试;被装饰函数用 wraps 才能内省、内置函数签名可能不全」,就掌握了 inspect 的应用。
六、总结与实践
总结函数内省:
函数是一等对象,有丰富的内省属性:
__name__ 函数名
__doc__ 文档字符串
__defaults__ 位置参数默认值(元组)
__kwdefaults__ 仅关键字参数默认值(字典)
__annotations__ 类型注解(字典)
__module__ 所属模块
__qualname__ 限定名
__code__ 代码对象(co_argcount/co_varnames/co_filename)
__dict__ 自定义属性
__wrapped__ 被 wraps 包装时指向原函数
获取参数签名(推荐 inspect):
inspect.signature(f) → Signature 对象(最优雅)
sig.parameters → 参数(name/kind/default/annotation)
sig.bind(args) → 校验/绑定参数
参数种类(param.kind):
仅位置/普通/*args/仅关键字/**kwargs
实践建议:
① 查参数用 inspect.signature(别自己读 __code__)
② 装饰器用 functools.wraps(保留元数据)
③ 框架读注解/参数做自动化(signature + __annotations__)
④ 内省有开销,别在热路径频繁用
核心总结:
函数一等对象、有内省属性(__name__/__doc__/__defaults__/__annotations__/__code__)
查签名用 inspect.signature(最优雅)
functools.wraps 靠内省属性保留元数据
所以函数内省属性(__name__/__doc__/__defaults__/__annotations__/__code__),查签名用 inspect.signature
函数是一等对象,有丰富的内省属性:__name__(函数名)、__doc__(文档)、__defaults__(位置参数默认值元组)、__kwdefaults__(仅关键字默认值字典)、__annotations__(类型注解字典)、__module__(模块)、__qualname__(限定名)、__code__(代码对象)、__dict__(自定义属性)、__wrapped__(原函数)。获取参数签名(推荐 inspect):inspect.signature(f) → Signature 对象(最优雅)、sig.parameters(参数 name/kind/default/annotation)、sig.bind(args)(校验/绑定)。参数种类:仅位置/普通/*args/仅关键字/**kwargs。实践建议:① 查参数用 inspect.signature、② 装饰器用 functools.wraps、③ 框架读注解/参数做自动化、④ 内省有开销别在热路径。理解「函数内省属性(name/doc/defaults/annotations/code);查签名用 inspect.signature 最优雅(parameters/bind);装饰器用 wraps 保留元数据;框架读注解做自动化」,就掌握了总结与实践。
记忆钩子:「Python 函数是『一等对象』——不只是可调用代码,还是带丰富元数据(属性)的对象,可运行时内省;★常用内省属性:name(函数名)、doc(文档字符串)、defaults(位置参数默认值元组、从右往左对应)、kwdefaults(仅关键字参数默认值字典)、annotations(类型注解字典、含 return)、module(所属模块)、qualname(限定名含类名)、code(代码对象:co_argcount 位置参数个数/co_varnames 变量名/co_filename 文件/co_firstlineno 行号)、dict(函数也能挂自定义属性)、wrapped(被 functools.wraps 包装时指向原函数);★获取参数签名的最佳工具是 inspect.signature(f)——返回 Signature 对象,sig.parameters 遍历每个参数(param.name/param.kind/param.default/param.annotation),param.kind 有五种(POSITIONAL_ONLY 仅位置/POSITIONAL_OR_KEYWORD 普通/VAR_POSITIONAL 即*args/KEYWORD_ONLY 仅关键字/VAR_KEYWORD 即kwargs),sig.bind(args)校验绑定参数;★强烈推荐 inspect.signature 而不是直接读__defaults__/code(后者要自己算参数对应关系繁琐易错),且 signature 会跟随__wrapped__正确处理被 wraps 装饰的函数;应用:functools.wraps 靠这些属性保留原函数元数据(否则装饰后__name__变 wrapper、文档丢失)、框架(FastAPI/pytest)读 signature+__annotations__做依赖注入和自动文档、参数校验、调试」**。
七、常见误区与追问
- 误区:函数就是一段代码,没有属性。 函数是「一等对象」——除了可调用,它还带有丰富的元数据属性:
__name__(函数名)、__doc__(文档字符串)、__defaults__(默认值)、__annotations__(类型注解)、__module__(所属模块)、__code__(代码对象)等,可以在运行时读取;甚至能给函数挂自定义属性(f.tag = "x",存在f.__dict__里);正是这些内省能力让装饰器、框架、文档工具能读取和处理函数的元信息。 - 误区:装饰一个函数后,它的 name 和 doc 不会变。 会变——如果装饰器没有用
functools.wraps,被装饰后的函数实际是内层的 wrapper 函数,f.__name__会变成'wrapper'、f.__doc__变成 wrapper 的文档(通常是 None)、原函数的签名也丢失;这会影响调试、日志、help()、以及依赖内省的框架;解决办法是在装饰器的 wrapper 上加@functools.wraps(原函数),它会把原函数的__name__/__doc__/__module__/__dict__/__wrapped__等拷贝过来。 - 误区:要拿函数的参数信息只能读 code 和 defaults。 不推荐这样——直接读
__code__.co_varnames、__code__.co_argcount、__defaults__、__kwdefaults__需要你自己把「参数名」和「默认值」「参数种类」对应起来(默认值是从右往左对应的、还要区分位置/关键字/可变参数),非常繁琐易错;应该用inspect.signature(f),它返回一个 Signature 对象、把每个参数封装成 Parameter(有.name/.kind/.default/.annotation),统一优雅、还能bind校验参数、并能跟随__wrapped__正确处理被装饰的函数。 - 误区:annotations 里的类型注解会在运行时强制检查参数类型。 不会——
__annotations__只是把类型注解「存起来」的字典({'a': int, 'return': bool}),Python 运行时不会用它做任何类型检查或强制转换;它的作用是「元数据」——供静态类型检查器(mypy)、IDE、以及框架(FastAPI 用它做请求参数解析和校验、pydantic 用它做数据验证)在需要时读取;也就是说注解本身是「被动的信息」,要不要用、怎么用取决于读取它的工具。 - 追问:Python 函数有哪些常用的内省属性,分别是什么? 主要有:
__name__(函数名字符串)、__doc__(文档字符串/docstring)、__defaults__(位置参数的默认值元组,从右往左对应有默认值的参数)、__kwdefaults__(仅关键字参数的默认值字典)、__annotations__(参数和返回值的类型注解字典,返回值的键是 ‘return’)、__module__(定义所在的模块名)、__qualname__(限定名,方法会带类名如 ‘Cls.method’)、__code__(代码对象,含co_argcount位置参数个数、co_varnames局部变量名、co_filename文件、co_firstlineno行号等底层信息)、__dict__(函数的自定义属性字典,函数也能挂属性)、__wrapped__(被 functools.wraps 包装时指向被包装的原函数);这些属性让运行时可以检查函数的名字、文档、参数、类型等元数据。 - 追问:为什么获取函数签名推荐用 inspect.signature 而不是直接读属性? 因为
inspect.signature(f)把参数信息封装成了统一、优雅的对象:它返回一个 Signature、其parameters是一个有序映射,每个参数是 Parameter 对象、带有.name(名字)、.kind(种类:仅位置/普通/*args/仅关键字/**kwargs)、.default(默认值,无默认是 Parameter.empty)、.annotation(类型注解);而直接读__defaults__/__kwdefaults__/__code__需要你自己把参数名和默认值、参数种类正确对应起来(默认值是从右往左对齐的、还要处理各种参数种类),既繁琐又容易出错;此外 signature 还有几个额外好处:能用sig.bind(args)校验和绑定实参(框架用它解析参数)、能读sig.return_annotation、并且会自动跟随__wrapped__找到被 functools.wraps 装饰的原函数的真实签名;所以查参数一律用 inspect.signature。 - 追问:函数内省在实际项目里有哪些应用? 很多「自动化魔法」都靠它:①
functools.wraps装饰器靠内省把原函数的__name__/__doc__/__module__/__wrapped__等拷贝到 wrapper 上,保证装饰后函数的元数据不丢;② Web 框架(如 FastAPI)读inspect.signature的参数名和__annotations__的类型注解,自动做请求参数解析、数据校验、生成 OpenAPI 文档;③ 测试框架(pytest)靠内省 fixture 的参数名做依赖注入;④ CLI 框架(Click、Typer)读参数和注解自动生成命令行接口;⑤ 依赖注入容器读构造函数/函数的参数类型自动装配依赖;⑥ 序列化、RPC、ORM 也用内省读取字段/参数;⑦ 调试和日志工具用__name__、__code__.co_filename/co_firstlineno、inspect.stack()定位代码位置;所以函数内省是 Python 元编程和框架自动化的基础能力。
八、加强记忆
Python 函数是「一等对象」——不只是可调用代码,还是带丰富元数据(属性)的对象、可运行时内省。常用内省属性:__name__(函数名)、__doc__(文档字符串)、__defaults__(位置参数默认值元组、从右往左对应)、__kwdefaults__(仅关键字参数默认值字典)、__annotations__(类型注解字典、含 return)、__module__(所属模块)、__qualname__(限定名含类名)、__code__(代码对象:co_argcount 位置参数个数/co_varnames 变量名/co_filename 文件/co_firstlineno 行号)、__dict__(函数也能挂自定义属性)、__wrapped__(被 functools.wraps 包装时指向原函数)。获取参数签名的最佳工具是 inspect.signature(f)——返回 Signature 对象,sig.parameters 遍历每个参数(.name/.kind/.default/.annotation),param.kind 有五种(仅位置/普通/*args/仅关键字/**kwargs),sig.bind(args) 校验绑定参数。强烈推荐 inspect.signature 而不是直接读 __defaults__/__code__(后者要自己算参数对应关系繁琐易错),且 signature 会跟随 __wrapped__ 正确处理被 wraps 装饰的函数。应用:functools.wraps 靠这些属性保留原函数元数据(否则装饰后 __name__ 变 wrapper、文档丢失)、框架(FastAPI/pytest)读 signature + __annotations__ 做依赖注入和自动文档。一句话「函数是一等对象、有内省属性(name/doc/defaults/annotations/code/dict);查参数签名用 inspect.signature(最优雅、parameters/bind、跟随__wrapped__);装饰器用 functools.wraps 保留元数据、框架读注解做自动化」。