Вот несколько ответов на мой вопрос.Я намереваюсь выбрать чей-либо ответ в качестве ответа, если сочту его более полным.
Кажется, нельзя избежать наложения протокола на функцию препроцессора, если только нет неясного способа обработки аргументов /головоломка kwargs изящно(Непонятно только потому, что я не знаю об этом.)
Вот несколько вариантов.
препроцесс возвращает (преобразован) args tuple
def wrap_args_deco(preprocess):
"""Preprocess needs to return the tuple of args (non-keyworded arguments)
that should be passed on to the decorated func."""
def decorator(func):
def func_wrapper(*args, **kwargs):
# NOTE: the only difference with input_wrap_decorator is the * before the preprocess
return func(*preprocess(*args, **kwargs))
return func_wrapper
return decorator
Пример использования:
def trans_args(adj, noun):
'''adj is capitalized and noun is quoted'''
return adj.upper(), '"{}"'.format(noun)
wrapped_func = wrap_args_deco(trans_args)(red_riding_hood)
assert wrapped_func('big', 'eyes') == 'What BIG "eyes" you have!'
Здесь есть некоторые ограничения.Например, вам нужно указать преобразования для всех аргументов, которые не имеют значений по умолчанию, независимо от того, хотите вы их преобразовать или нет.
препроцесс возвращает (преобразуется) kwargs dict
def wrap_kwargs_deco(preprocess):
"""Preprocess needs to return the dict of kwargs (keyworded, or named arguments)
that should be passed on to the decorated func."""
def decorator(func):
def func_wrapper(*args, **kwargs):
# NOTE: the only difference with input_wrap_decorator is the ** before the preprocess
return func(**preprocess(*args, **kwargs))
return func_wrapper
return decorator
Пример:
def trans_kwargs(adj, noun):
'''adj is capitalized and noun is quoted'''
return {'adj': adj.upper(), 'noun': '"{}"'.format(noun)}
wrapped_func = wrap_kwargs_deco(trans_kwargs)(red_riding_hood)
assert wrapped_func('big', 'eyes') == 'What BIG "eyes" you have!'
препроцесс возвращает (преобразованный) (args, kwargs) кортеж
Вы можете получить (sorta) лучшийиз обоих миров при наличии функции preprocess возвращают преобразованные args (кортеж) и kwargs (dict).
def wrap_args_and_kwargs_deco(preprocess):
"""Preprocess needs to return a the tuple (arg, kwargs) where
arg is the list/tuple of (transformed) non-keyworded arguments and
kwargs is the dict of (transformed) keyworded (a.k.a. "named") arguments
that should be passed on to the decorated func."""
def decorator(func):
def func_wrapper(*args, **kwargs):
args, kwargs = preprocess(*args, **kwargs)
return func(*args, **kwargs)
return func_wrapper
return decorator
Пример:
def trans_kwargs(adj, noun):
return (adj.upper(),), {'noun': '"{}"'.format(noun)}
wrapped_func = wrap_args_and_kwargs_deco(trans_kwargs)(red_riding_hood)
assert wrapped_func('big', 'eyes') == 'What BIG "eyes" you have!'
указание преобразователей для отдельных аргументов (с ключевым словом)
Варианты, предложенные выше, являются более общими, чем следующиев том смысле, что они могут основывать свою логику преобразования на глобальном представлении всех значений аргументов.Но в большинстве случаев вам, вероятно, потребуется преобразовать аргумент только на основе его имени.В этом случае вот решение этой проблемы, предлагая более приятный интерфейс.
from functools import wraps
def transform_args(**trans_func_for_arg):
"""
Make a decorator that transforms function arguments before calling the function.
Works with plain functions and bounded methods.
"""
def transform_args_decorator(func):
if len(trans_func_for_arg) == 0: # if no transformations were specified...
return func # just return the function itself
else:
@wraps(func)
def transform_args_wrapper(*args, **kwargs):
# get a {argname: argval, ...} dict from *args and **kwargs
# Note: Didn't really need an if/else here but I am assuming that...
# Note: ... getcallargs gives us an overhead that can be avoided if there's only keyword args.
if len(args) > 0:
val_of_argname = inspect.signature(func).bind_partial(*args, **kwargs).arguments
else:
val_of_argname = kwargs
for argname, trans_func in trans_func_for_arg.items():
val_of_argname[argname] = trans_func(val_of_argname[argname])
# apply transform functions to argument values
return func(**val_of_argname)
return transform_args_wrapper
return transform_args_decorator
Вот пример с большей функциональностью, чем у других:
# Example with a plain function
def f(a, b, c='default c'):
return "a={a}, b={b}, c={c}".format(a=a, b=b, c=c)
def prepend_root(x):
return 'ROOT/' + x
def test(f):
assert f('foo', 'bar', 3) == 'a=foo, b=bar, c=3'
ff = transform_args()(f) # no transformation specification, so function is unchanged
assert ff('foo', 'bar', 3) == 'a=foo, b=bar, c=3'
ff = transform_args(a=prepend_root)(f) # prepend root to a
assert ff('foo', 'bar', 3) == 'a=ROOT/foo, b=bar, c=3'
ff = transform_args(b=prepend_root)(f) # prepend root to b
assert ff('foo', 'bar', 3) == 'a=foo, b=ROOT/bar, c=3'
ff = transform_args(a=prepend_root, b=prepend_root)(f) # prepend root to a and b
assert ff('foo', 'bar', 3) == 'a=ROOT/foo, b=ROOT/bar, c=3'
test(f)
# Example with a bounded method
class A:
def __init__(self, sep=''):
self.sep = sep
def f(self, a, b, c):
return f"a={a}{self.sep} b={b}{self.sep} c={c}"
a = A(sep=',')
test(a.f)
# Example of decorating the method on the class itself
A.f = transform_args(a=prepend_root, b=prepend_root)(A.f)
a = A(sep=',')
assert a.f('foo', 'bar', 3) == 'a=ROOT/foo, b=ROOT/bar, c=3'