Python装饰器与函数签名的关系
2026/6/16 3:51:54 网站建设 项目流程

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询