当前位置: 首页 > 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.jsqmd.com/news/1020929/

相关文章:

  • Linux 调度器优化:从 CFS 到实时调度的性能调优实践
  • 伯努利分布:二元建模的底层协议与工程实践
  • 3大痛点解决:Windows上直接安装APK文件的革命性方案
  • 解锁暗黑破坏神2存档编辑新维度:d2s-editor技术探索与实践路径
  • 模拟芯片ESD防护版图设计:从核心原理到实战布局布线
  • Python生成器与状态机实现
  • 2026年医院室内空气净化服务商推荐:病房与候诊区治理选型指南 - 观域传媒
  • 【安徽大学主办,权威背书 | IEEE出版,EI 检索稳定 | 连续四届全部论文完成见刊检索,每届都在提交后2-3个月检索 | 设奖项评选】第五届半导体与电子技术国际研讨会(ISSET 2026)
  • 探秘手机号码地理位置定位:开源实现的技术解析与应用实践
  • 混淆矩阵:二分类模型评估的核心工具与业务洞察指南
  • D2R Pixel Bot:暗黑破坏神2重制版终极自动化解决方案
  • 2026年郑州正规装修公司排行:郑州新房毛坯装修/郑州装修公司/郑州复式装修/郑州大平层装修/郑州全屋翻新/选择指南 - 优质品牌商家
  • 2026年一流车企,一致之选:五代桩能效U7背后的车规级验证体系
  • 复杂模型机构建实战:从架构设计到电商销量预测系统落地
  • 3步实现Windows电脑接收AirPlay投屏:完全免费开源方案指南
  • FoundationPose:零样本6D物体姿态估计基础模型实践指南
  • 基于RV1126的智能视觉系统开发:从硬件选型到AI模型部署全流程解析
  • 2026年义乌本地驾校教练怎么选?青口、佛堂、苏溪等区域教练真实对比分析 - 优质品牌商家
  • Vue动态组件+异步组件实战:Tab切换、按需加载、KeepAlive缓存,一次搞定
  • 法向应力与剪切应力:工程力学核心概念深度解析与应用实战
  • 终极指南:如何用LightBulb自动调节屏幕色温保护眼睛健康
  • 如何轻松下载网页视频?这款免费Chrome插件3分钟帮你搞定
  • 【Zephyr开发系列-8】Zephyr CMake构建解析
  • codex和open claude两者只有客户端工具开源,底层大模型权重全部闭源
  • 2026年水族滤材选购指南:滤材什么牌子值得买及专业选型标准 - 华旭传媒
  • 如何打造一个支持40+漫画源的Android阅读器:Cimoc技术深度解析
  • 2026年家用电梯安装公司哪家好?多品牌对比与真实案例深度解析 - 优质品牌商家
  • TwinCAT 3 下载与安装指南
  • 嵌入式Flash存储管理:fls模块原理、配置与高可靠应用实战
  • Windows Python 3.8下rasterio 1.3.10 wheel文件安装与GIS开发环境配置指南