← 返回题目列表

Python 怎么获取函数的名字、参数、签名?函数有哪些内省属性?

高频 中等 第 11 / 21 题 更新于 2026/07/31
Python函数内省inspectsignature

简化版

**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.argumentsbound.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__ 里);正是这些内省能力让装饰器、框架、文档工具能读取和处理函数的元信息。
  • 误区:装饰一个函数后,它的 namedoc 不会变。 会变——如果装饰器没有用 functools.wraps,被装饰后的函数实际是内层的 wrapper 函数,f.__name__ 会变成 'wrapper'f.__doc__ 变成 wrapper 的文档(通常是 None)、原函数的签名也丢失;这会影响调试、日志、help()、以及依赖内省的框架;解决办法是在装饰器的 wrapper 上加 @functools.wraps(原函数),它会把原函数的 __name__/__doc__/__module__/__dict__/__wrapped__ 等拷贝过来。
  • 误区:要拿函数的参数信息只能读 codedefaults 不推荐这样——直接读 __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_firstlinenoinspect.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 保留元数据、框架读注解做自动化」。