当前位置: 首页 > news >正文

Python装饰器与函数签名的关系

Python装饰器与函数签名的关系

装饰器包装函数后,原始函数的签名信息会丢失。inspect.signature正确获取签名的唯一方式是使用functools.wraps。

演示签名丢失:

import inspect

def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper

@log_calls
def process_data(name: str, count: int = 0) -> bool:
"""Process the data."""
return True

sig = inspect.signature(process_data)
print(sig) # (*args, **kwargs) 而不是 (name: str, count: int = 0)

wrapper完全隐藏了原始函数签名。inspect.signature返回wrapper的参数列表。

使用wraps修复签名:

from functools import wraps

def log_calls_proper(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper

@log_calls_proper
def process_data(name: str, count: int = 0) -> bool:
return True

sig = inspect.signature(process_data)
print(sig) # (name: str, count: int = 0)

wraps将__name__、__doc__、__annotations__复制到wrapper。inspect.signature从__annotations__恢复签名。

部分修复的局限性:

@log_calls_proper
def process_data(name: str, count: int = 0) -> bool:
return True

print(process_data.__name__) # process_data
print(process_data.__doc__) # Process the data.
print(process_data.__annotations__) # {'name': str, 'count': int, 'return': bool}

wraps不修复wrapper的默认参数行为。如果用户对wrapper调用inspect.signature,返回的签名有正确类型但无法动态修改。

手动修复__signature__:

import inspect
from functools import wraps

def preserve_signature(func):
sig = inspect.signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.__signature__ = sig
return wrapper

@preserve_signature
def compute(a: int, b: int = 0) -> int:
return a + b

sig = inspect.signature(compute)
print(sig) # (a: int, b: int = 0) -> int

__signature__显式设置签名。inspect.signature优先使用__signature__属性。

装饰器接收参数时的签名保持:

def with_logging(level='INFO'):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.__signature__ = inspect.signature(func)
return wrapper
return decorator

@with_logging(level='DEBUG')
def connect(host: str, port: int = 80) -> None:
pass

sig = inspect.signature(connect)
print(sig) # (host: str, port: int = 80) -> None

多层闭包和装饰器时签名保持:

def compose_decorators(*decorators):
def decorator(func):
result = func
for dec in reversed(decorators):
result = dec(result)
return result
return decorator

@compose_decorators(
with_logging('INFO'),
preserve_signature
)
def query(sql: str, params: tuple = ()) -> list:
return []

sig = inspect.signature(query)
# 应保持 (sql: str, params: tuple = ()) -> list

对于链式装饰器,只有正确实现了wraps或__signature__的装饰器才能保持签名。

带签名的装饰器类实现:

class Timed:
def __init__(self, func):
wraps(func)(self)
self.func = func

def __call__(self, *args, **kwargs):
import time
start = time.perf_counter()
result = self.func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{self.func.__name__} took {elapsed:.4f}s")
return result

def __get__(self, obj, objtype=None):
if obj is None:
return self
return functools.partial(self.__call__, obj)

@Timed
def heavy_compute(n: int) -> int:
return sum(range(n))

sig = inspect.signature(heavy_compute)
print(sig) # (n: int) -> int

wraps(self)将func的元信息复制到Timed实例。__call__接受任何参数。

使用装饰器工厂根据条件修改签名:

import inspect
from functools import wraps

def inject_param(name, default=None, kind=inspect.Parameter.KEYWORD_ONLY):
def decorator(func):
sig = inspect.signature(func)
new_param = inspect.Parameter(
name, kind, default=default
)
params = list(sig.parameters.values())
if kind == inspect.Parameter.KEYWORD_ONLY:
params.append(new_param)
else:
params.insert(0, new_param)
new_sig = sig.replace(parameters=params)

@wraps(func)
def wrapper(*args, **kwargs):
kwargs[name] = kwargs.get(name, default)
return func(*args, **kwargs)

wrapper.__signature__ = new_sig
return wrapper
return decorator

@inject_param('debug', False)
def handle_request(path: str):
pass

sig = inspect.signature(handle_request)
# (path: str, debug: bool = False)

inspect.Parameter创建新的参数对象。参数种类有POSITIONAL_ONLY、POSITIONAL_OR_KEYWORD、VAR_POSITIONAL、KEYWORD_ONLY、VAR_KEYWORD。

getfullargspec的局限性(已弃用):

import inspect

def decorated():
pass

print(inspect.getfullargspec(decorated)) # 可能返回不正确的信息

getfullargspec只检查__code__和__defaults__,不支持__signature__。应该使用inspect.signature。

Signature实例的bind方法将参数绑定到签名上:

sig = inspect.signature(handle_request)
bound = sig.bind("/home", debug=True)
print(bound.arguments) # {'path': '/home', 'debug': True}

try:
sig.bind("/home", extra=True)
except TypeError as e:
print(e) # got unexpected keyword argument 'extra'

bind验证参数是否合法。bind_partial允许部分绑定。

装饰器内部通过signature实现参数校验:

import inspect

def validate(func):
sig = inspect.signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
for name, value in bound.arguments.items():
param = sig.parameters[name]
if param.annotation != inspect.Parameter.empty:
expected = param.annotation
if not isinstance(value, expected):
raise TypeError(
f"Argument {name} must be {expected.__name__}, "
f"got {type(value).__name__}"
)
return func(*args, **kwargs)
wrapper.__signature__ = sig
return wrapper

http://www.zskr.cn/news/1532775.html

相关文章:

  • 伯努利分布:二元建模的底层协议与工程实践
  • 2026年公共卫生间隔断批发市场深度观察:板材选型、成本对比与供应商实测分析 - 优质品牌商家
  • 混淆矩阵:二分类模型评估的核心工具与业务洞察指南
  • 探秘手机号码地理位置定位:开源实现的技术解析与应用实践
  • 2026年郑州正规装修公司排行:郑州新房毛坯装修/郑州装修公司/郑州复式装修/郑州大平层装修/郑州全屋翻新/选择指南 - 优质品牌商家
  • 复杂模型机构建实战:从架构设计到电商销量预测系统落地
  • Python生成器与状态机实现
  • 2026年义乌本地驾校教练怎么选?青口、佛堂、苏溪等区域教练真实对比分析 - 优质品牌商家
  • 法向应力与剪切应力:工程力学核心概念深度解析与应用实战
  • 【Zephyr开发系列-8】Zephyr CMake构建解析
  • 如何打造一个支持40+漫画源的Android阅读器:Cimoc技术深度解析
  • AI Agent架构设计实战:从ReAct到多智能体协作的完整指南
  • TwinCAT 3 下载与安装指南
  • 5分钟搞定复古音频宝藏:用Platinum-MD让MiniDisc重获新生
  • 2026年桑拿设备与温泉池工程市场观察:四川及西南地区服务商综合评估 - 优质品牌商家
  • 分布式互斥算法Guilbaud-Pham:原理、实现与工程实践
  • LDO误差放大器输出端接Buffer对环路直流增益的影响分析
  • 有哪些食品配餐类上市公司? - 品牌2026
  • 深度解析macOS核心架构:从Darwin内核到Apple Silicon演进
  • Python仿真方波分解与合成:傅里叶级数原理与信号处理实践
  • Google depot_tools工具集:大型C++项目开发的瑞士军刀
  • 2026年淄博酒店瓷与连锁餐饮餐具供应商综合实力观察:谁在引领行业升级? - 优质品牌商家
  • Rider for Unity:提升Unity开发效率的智能IDE深度解析
  • 如何在5分钟内用ta4j构建你的第一个交易策略:Java技术分析库完全指南
  • NoC组件之Router微架构解析(八)虚通道分配的延迟优化
  • 深度解析 Kimi-K2.7-Code:万亿参数编程模型技术拆解 + startapi.top 接口实战调用(附完整代码)
  • 反激变换器设计精髓:从原理到面试的系统工程思维
  • Windows此电脑清理终极指南:告别顽固快捷方式,打造个性化工作空间
  • XCOM 2模组管理新范式:AML启动器的技术架构与应用实践
  • 从信创到“AI+信创”:中间件缘何成为这场变革的关键胜负手?