gpt4 book ai didi

python - 签名更 retrofit 饰器 : properly documenting additional argument

转载 作者:行者123 更新时间:2023-11-28 17:30:59 25 4
gpt4 key购买 nike

假设我有一个自定义装饰器,我希望它能够正确处理装饰函数的文档字符串。问题是:我的装饰器添加了一个参数。

from functools import wraps

def custom_decorator(f):
@wraps(f)
def wrapper(arg, need_to_do_more):
'''
:param need_to_do_more: if True: do more
'''
args = do_something(arg)

if need_to_do_more:
args = do_more(args)

return f(args)

return wrapper

你可以看到参数实际上并没有传递给装饰函数,而是被包装器使用——这可能是也可能是在这里不相关。

如何正确处理记录附加参数?包装器采用额外参数是一种好习惯,还是我应该避免它?

或者我应该使用不同的解决方案,例如:

  • 使包装器成为一个简单的高阶函数,并将它调用的函数作为第三个参数传递
  • 将包装器重构为两个独立的函数?

最佳答案

所以 - __doc__ 除了这很棘手 - 而且,由于越来越多的开发人员在编码时依赖自动参数建议,这是由 IDE 自省(introspection)提供的,任何装饰器都确实需要它将向函数添加额外的命名参数。

我在 a project I am developing 中做到了这一点,解决方案是创建一个新的虚拟函数,它将显示所需的组合签名 - 然后使用这个新的虚拟函数作为 @wraps 调用的参数,。

这是我的代码 - 它已经足够好了,因此与其他项目无关,我可能很快就会将它放入装饰器 Python 包中。现在:


def combine_signatures(func, wrapper=None):
"""Adds keyword-only parameters from wrapper to signature

Use this in place of `functools.wraps`
It works by creating a dummy function with the attrs of func, but with
extra, KEYWORD_ONLY parameters from 'wrapper'.
To be used in decorators that add new keyword parameters as
the "__wrapped__"

Usage:

def decorator(func):
@combine_signatures(func)
def wrapper(*args, new_parameter=None, **kwargs):
...
return func(*args, **kwargs)
"""
# TODO: move this into 'extradeco' independent package
from functools import partial, wraps
from inspect import signature, _empty as insp_empty, _ParameterKind as ParKind
from itertools import groupby

if wrapper is None:
return partial(combine_signatures, func)

sig_func = signature(func)
sig_wrapper = signature(wrapper)
pars_func = {group:list(params) for group, params in groupby(sig_func.parameters.values(), key=lambda p: p.kind)}
pars_wrapper = {group:list(params) for group, params in groupby(sig_wrapper.parameters.values(), key=lambda p: p.kind)}

def render_annotation(p):
return f"{':' + (repr(p.annotation) if not isinstance(p.annotation, type) else repr(p.annotation.__name__)) if p.annotation != insp_empty else ''}"

def render_params(p):
return f"{'=' + repr(p.default) if p.default != insp_empty else ''}"

def render_by_kind(groups, key):
parameters = groups.get(key, [])
return [f"{p.name}{render_annotation(p)}{render_params(p)}" for p in parameters]

pos_only = render_by_kind(pars_func, ParKind.POSITIONAL_ONLY)
pos_or_keyword = render_by_kind(pars_func, ParKind.POSITIONAL_OR_KEYWORD)
var_positional = [p for p in pars_func.get(ParKind.VAR_POSITIONAL,[])]
keyword_only = render_by_kind(pars_func, ParKind.KEYWORD_ONLY)
var_keyword = [p for p in pars_func.get(ParKind.VAR_KEYWORD,[])]

extra_parameters = render_by_kind(pars_wrapper, ParKind.KEYWORD_ONLY)

def opt(seq, value=None):
return ([value] if value else [', '.join(seq)]) if seq else []

annotations = func.__annotations__.copy()
for parameter in pars_wrapper.get(ParKind.KEYWORD_ONLY):
annotations[parameter.name] = parameter.annotation

param_spec = ', '.join([
*opt(pos_only),
*opt(pos_only, '/'),
*opt(pos_or_keyword),
*opt(keyword_only or extra_parameters, ('*' if not var_positional else f"*{var_positional[0].name}")),
*opt(keyword_only),
*opt(extra_parameters),
*opt(var_keyword, f"**{var_keyword[0].name}" if var_keyword else "")
])
declaration = f"def {func.__name__}({param_spec}): pass"

f_globals = func.__globals__
f_locals = {}

exec(declaration, f_globals, f_locals)

result = f_locals[func.__name__]
result.__qualname__ = func.__qualname__
result.__doc__ = func.__doc__
result.__annotations__ = annotations

return wraps(result)(wrapper)

在交互模式下测试得到这样的结果:

IPython 7.8.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from terminedia.utils import combine_signatures

In [2]: def add_color(func):
...: @combine_signatures(func)
...: def wrapper(*args, color=None, **kwargs):
...: global context
...: context.color = color
...: return func(*args, **kw)
...: return wrapper
...:

In [3]: @add_color
...: def line(p1, p2):
...: pass
...:

In [4]: line
Out[4]: <function __main__.line(p1, p2, *, color=None)>

(至于 doc 字符串,就像问题中一样 - 一旦获得了所有包装器和函数数据,这是粘贴之前的文本处理问题 result.__doc__ = func.__doc__ 。因为每个项目在 docstrings 中都有不同的文档参数样式,它不能以“一刀切”的方式可靠地完成,但是通过一些字符串拼接和测试,它可以针对任何给定的文档字符串样式进行完善)

关于python - 签名更 retrofit 饰器 : properly documenting additional argument,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34402773/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com