使用不修改函数

时间:2017-12-12 23:51:28

标签: python python-3.x function scope decorator

编辑:装饰者应该在运行时执行该功能,而不是修改它。

我希望能够在脚本开始时使用参数执行函数顶部添加装饰器,并打印结果(作为示例)。

类似于下面的代码,除了下面的代码不起作用,因为我不知道将参数传递给它的简洁方法。我不想修改原始功能,只需要像#34; test"解释函数时的部分。

def test_decorator(func, *args, **kwargs):  # I'm aware this doesn't work. I'm modeling it after class methods for simplicity sake.
    print("Passed:", args, kwargs,
          "\nResult:", func(*args, **kwargs))

    return func


@test_decorator(70, 20, 10, "last_arg_example:")
def test_example(arg1, arg2, arg3=None, arg4="arg4_example:"):
    print(arg4, arg1 + arg2 + (arg3 if arg3 else 0))

我知道装饰器定义的第一行搞砸了,但我也不清楚如何修复它。我能找到的所有东西都使用"包装"各种各样,但我不想修改原件,就像我说的那样。

如果函数不需要参数,我也应该只用它上面的@test_decorator来装饰它。或者即使它确实需要参数,也不对实际功能做任何事情并完全打印其他内容。

我该怎么做?

修改 为了更简洁,我希望装饰器执行跟随它的函数,并将参数传递给装饰器并打印输出。

>>> @decorator(4, 5)
>>> def test1(arg1, arg2):
...    return arg1 + arg2
...
9
>>>

进一步的功能是通过不向函数传递任何参数来处理无参数。以下是两种情况:

>>> @decorator
>>> def test2(arg1=4, arg2=5):
...    return arg1 + arg2
...
9
>>>

以下是参数没有默认值:

>>> @decorator
>>> def test1(arg1, arg2):
...    return arg1 + arg2
...
Traceback (most recent call last):
 File "<stdin>", line 1 in <module>
TypeError: test1() takes exactly 2 arguments (0 given)
>>>

请记住,我知道交互式翻译不会像这样工作。我实际上没有试过这些例子;然后我输入了。我无论如何都无法测试这些例子,因为我没有为他们设计工作装置。

2 个答案:

答案 0 :(得分:1)

你是说这样的意思吗?当你传递一个装饰器函数参数时,它必须返回一个“普通”装饰器 - 一个只接受原始函数作为其参数的装饰器。显式传递一个或多个参数的装饰器实际上是一个“装饰器 - 工厂”函数,它返回实际的装饰器,然后我们将其应用于以下函数定义。

据我所知,你的装饰者不使用任何自己的参数 - 70, 20, 10, "last_arg_example:"值 - 所以在下面的代码中它们只是打印然后传递在原始功能上获得使用它们的结果,也打印出来。所以,在所有这些披露者都不在的情况下,这里有一个例子说明了我的概述:

def test_decorator(*deco_args, **deco_kwargs):

    def decorator(func):
        print('Decorator-factory called: {}({}, {})'.format(
            func.__name__,
            ', '.join(repr(arg) for arg in deco_args),
            ', '.join('{}={!r}'.format(k, v) for k, v in deco_kwargs)))

        results = func(*deco_args, **deco_kwargs)
        print("    function returned: {!r}".format(results))

        def wrapped(*args, **kwrds):  # A no-op decoration.
            return func(*args, **kwrds)

        return wrapped

    return decorator

print('Defining decorated test_example() function:')
@test_decorator(70, 20, 10, "last_arg_example:")
def test_example(arg1, arg2, arg3=None, arg4="arg4_example:"):
    print(arg4, arg1 + arg2 + (arg3 if arg3 else 0))

print()
print('Calling decorated test_example() function:')
test_example('arg1', 'arg2', arg3='Not None')

输出:

Defining decorated test_example() function:
Decorator-factory called: test_example(70, 20, 10, 'last_arg_example:', )
last_arg_example: 100
    function returned: None

Calling decorated test_example() function:
arg4_example: arg1arg2Not None

答案 1 :(得分:1)

@ martineau的答案涵盖了如何使装饰工厂接受参数并返回适当的装饰器。如果您使用此模式,您的实际装饰器可以返回未修改的原始函数:

def test_decorator(*args, **kwargs):
    def decorator(func):
        return func
    return decorator

与原始规范的唯一区别在于您必须在无参数情况下明确调用工厂:@test_decorator()而不仅仅是@test_decorator

现在,您可以在工厂或装饰器中执行任何操作,包括在某处注册函数调用。例如:

call_list = []

def test_decorator(*args, **kwargs):
    def decorator(func):
        def make_call():
            return func(*args, **kwargs)
        call_list.append(make_call)
        # Decorated function is completely unchanged
        return func
    return decorator

@test_decorator(70, 20, 10, "last_arg_example:")
@test_decorator(60, 10, 20, "middle_arg_example:")
@test_decorator(50, 0, 30, "first_arg_example:")
def test_example(arg1, arg2, arg3=None, arg4="arg4_example:"):
    print(arg4, arg1 + arg2 + (arg3 if arg3 else 0))

for item in call_list:
    item()

这会在首次导入模块时注册call_list中的每个修饰函数调用,然后依次运行修饰后的调用。它不会以任何方式修改实际修饰的函数。另一个好的副作用是你可以将工厂嵌套任意次数,如图所示。每个内部装饰的返回值被传递给外部装饰,这意味着注册函数调用的顺序是“向后”。以上结果是:

first_arg_example: 80
middle_arg_example: 90
last_arg_example: 100

有关类装饰器的更多信息

您可以将装饰器工厂重新格式化为类。装饰器(用于您的目的)是任何可调用的函数,它接受函数并返回函数。这意味着具有__call__方法的类的实例也可以是装饰器。上面的例子可以改写为

call_list = []

class TestDecorator:
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def __call__(self, func):
        def make_call():
            return func(*self.args, **self.kwargs)
        call_list.append(make_call)
        # Decorated function is completely unchanged
        return func

@TestDecorator(70, 20, 10, "last_arg_example:")
@TestDecorator(60, 10, 20, "middle_arg_example:")
@TestDecorator(50, 0, 30, "first_arg_example:")
def test_example(arg1, arg2, arg3=None, arg4="arg4_example:"):
    print(arg4, arg1 + arg2 + (arg3 if arg3 else 0))

for item in call_list:
    item()