简体   繁体   English

Python 装饰器可选参数

[英]Python decorator optional argument

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print f.__name__  # prints 'f'
print f.__doc__   # prints 'does some math'

Given this sample code, how would I be able to do @logged(variable) ?鉴于此示例代码,我将如何执行@logged(variable)

I tried this我试过这个

from functools import wraps
def logged(func):
    def outer(var):
        @wraps(func)
        def with_logging(*args, **kwargs):
            print func.__name__ + " was called"
            return func(*args, **kwargs)
        return with_logging
    return outer

I was hoping to execute like this: logged(func)(session_variable)我希望像这样执行:logged(func)(session_variable)

But doesn't work.但不起作用。 Any idea?任何的想法? I want to be able to do @logged and @logged(var) ( or even @logged(var1, var2)) Thanks.我希望能够做到@logged 和@logged(var)(甚至@logged(var1, var2))谢谢。

The trick here is, you have to introspect what you are given:这里的诀窍是,你必须反省你得到的东西:

def logged(*setting_args, **setting_kwargs):
    no_args = False
    if len(setting_args) == 1 \
        and not setting_kwargs \
        and callable(setting_args[0]):
        # We were called without args
        func = setting_args[0]
        no_args = True

    def outer(func):
        @wraps(func)
        def with_logging(*args, **kwargs):
            print "{} was called".format(func.__name__)
            print "Setting args are: {}".format(setting_args)
            print "Setting keyword args are: {}".format(setting_kwargs)
            return func(*args, **kwargs)
        return with_logging

    if no_args:
        return outer(func)
    else:
        return outer

This will work with any of the following:这将适用于以下任何一项:

# No arguments
@logged
def some_function(x):
    pass

# One or more arguments
@logged(1, 2, 3)
def some_function(x):
    pass

# One or more keyword arguments
@logged(key=1, another_key=2)
def some_function(x):
    pass

# A mix of the two
@logged(1, 2, key=3)
def some_function(x):
    pass

It will not work if it is called with only one callable argument:如果只使用一个可调用参数调用它,它将不起作用:

# This will break.
@logged(lambda: "Just for fun")
def some_function(x):
    pass

There is no way to tell the difference between a single callable setting and a no-arg invocation of the decorator.无法区分单个可调用设置和装饰器的无参数调用之间的区别。 However, you can pass a garbage keyword arg to get around even that if you need to:但是,如果您需要,您可以传递垃圾关键字 arg 来解决这个问题:

# This gets around the above limitation
@logged(lambda: "Just for fun", ignored=True)
def some_function(x):
    pass

The question is more than 6 years old, and already has answers.这个问题6年多了,已经有了答案。 I bumped into same situation - had to update a decorator used at lot of places in the code, and wanted to add an optional argument.我遇到了同样的情况 - 不得不更新代码中很多地方使用的装饰器,并想添加一个可选参数。

I am able to get it done, using a different approach - from the book Python CookBook 3rd Edition , Chapter 9 - 9.6.我能够使用不同的方法来完成它 - 来自Python CookBook 3rd Edition第 9 章 - 9.6 一书。 Defining a Decorator That Takes an Optional Argument.定义一个带有可选参数的装饰器 It takes the problem, presents the solution and end it with a discussion (just great).它提出问题,提出解决方案并以讨论结束(很棒)。


Solution : for Python 3.3+解决方案:对于Python 3.3+

from functools import wraps, partial

def logged(func=None, *, var1=None, var2=None):
    if func is None:
        return partial(logged, var1=var1, var2=var2)

    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

with above you can do either of the following:使用上述方法,您可以执行以下任一操作:

@logged
def f(x):

@logger(var1)
def f(x):

@logger(var1, var2)
def f(x) 

Explanation (best to look for it in the book)说明(最好在书上找)

To understand how the code works, you need to have a firm understanding of how decorators get applied to functions and their calling conventions.要了解代码的工作原理,您需要对装饰器如何应用于函数及其调用约定有一个深入的了解

1. Simple decorator such as this: 1. 简单的装饰器,例如:

# Example use
@logged
def add(x, y):
    return x + y

The calling sequence is as follows:调用顺序如下:

def add(x, y):
    return x + y

add = logged(add)

In this case, the function to be wrapped is simply passed to logged as the first argument.在这种情况下,要包装的函数只是作为第一个参数传递给日志记录。 Thus, in the solution, the first argument of logged() is the function being wrapped.因此,在解决方案中,logged() 的第一个参数是被包装的函数。 All of the other arguments must have default values.所有其他参数都必须具有默认值。

2. Decorator taking arguments: 2. 装饰器取参数:

@logged(level=logging.CRITICAL, name='example')
def spam():
    print('Spam!')

The calling sequence is as follows:调用顺序如下:

def spam():
    print('Spam!')

spam = logged(level=logging.CRITICAL, name='example')(spam)

The above last line, is how a decorator with arguments is called ie in the initial invocation of logged() the function to be decorated spam() is not passed, thus we set it optional in decorator ie func=None in logged definition.上面的最后一行,是如何调用带参数的装饰器,即在首次调用logged() ,未传递要装饰的函数spam() ,因此我们在装饰器中将其设置为可选,即在logged定义中func=None So, in the first call only the arguments are passed.因此,在第一次调用中只传递参数。

This, in turn, forces the other arguments to be specified by keyword.这反过来又强制其他参数由关键字指定。 Furthermore, when arguments are passed, a decorator is supposed to return a function that accepts the function and wraps it (see Recipe 9.5).此外,当传递参数时,装饰器应该返回一个接受函数并包装它的函数(参见 9.5 节)。 To do this, the solution uses a clever trick involving functools.partial .为此,该解决方案使用了一个巧妙的技巧,涉及functools.partial Specifically, it simply returns a partially applied version of itself where all arguments are fixed except for the function to be wrapped .具体来说,它只是返回自身的部分应用版本,其中除了要包装的函数外,所有参数都是固定的

put the def outer(var) outward, that isdef outer(var)向外放,即

def outer(var):
    def logged(func):
        ...

, then use @outer(somevar) for your function, this would work. ,然后将@outer(somevar)用于您的函数,这将起作用。

There is minor error in the code you tried.您尝试的代码中存在小错误。 Instead of creating nested functions with arguments as func > var > *args, **kwargs , the order should be var > func > *args, **kwargs .而不是创建带有参数的嵌套函数func > var > *args, **kwargs ,顺序应该是var > func > *args, **kwargs

Below is the code snippet that would suffice your requirement.以下是满足您要求的代码片段。

from functools import wraps

def logged(var=None):
    def outer(func):
        @wraps(func)
        def with_logging(*args, **kwargs):
            print func.__name__ + " was called"
            return func(*args, **kwargs)
        return with_logging
    return outer

You may call this decorator as:你可以称这个装饰器为:

@logged
def func1():
    ...

OR,或者,

@logged(xyz)
def func2():
    ...

To know more on how decorators work, refer article Decorators with optional arguments .要了解有关装饰器如何工作的更多信息,请参阅带有可选参数的装饰器一文。

A possible alternative solution to Sean Vieira's could be: Sean Vieira 的一个可能的替代解决方案可能是:

from functools import wraps
import inspect


def decorator_defaults(**defined_defaults):
    def decorator(f):
        args_names = inspect.getargspec(f)[0]

        def wrapper(*new_args, **new_kwargs):
            defaults = dict(defined_defaults, **new_kwargs)
            if len(new_args) == 0:
                return f(**defaults)
            elif len(new_args) == 1 and callable(new_args[0]):
                return f(**defaults)(new_args[0])
            else:
                too_many_args = False
                if len(new_args) > len(args_names):
                    too_many_args = True
                else:
                    for i in range(len(new_args)):
                        arg = new_args[i]
                        arg_name = args_names[i]
                        defaults[arg_name] = arg
                if len(defaults) > len(args_names):
                    too_many_args = True
                if not too_many_args:
                    final_defaults = []
                    for name in args_names:
                        final_defaults.append(defaults[name])
                    return f(*final_defaults)
                if too_many_args:
                    raise TypeError("{0}() takes {1} argument(s) "
                                    "but {2} were given".
                                    format(f.__name__,
                                           len(args_names),
                                           len(defaults)))
        return wrapper
    return decorator


@decorator_defaults(start_val="-=[", end_val="]=-")
def my_text_decorator(start_val, end_val):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            return "".join([f.__name__, ' ', start_val,
                            f(*args, **kwargs), end_val])
        return wrapper
    return decorator


@decorator_defaults(end_val="]=-")
def my_text_decorator2(start_val, end_val):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            return "".join([f.__name__, ' ', start_val,
                            f(*args, **kwargs), end_val])
        return wrapper
    return decorator


@my_text_decorator
def func1a(value):
    return value


@my_text_decorator()
def func2a(value):
    return value


@my_text_decorator2("-=[")
def func2b(value):
    return value


@my_text_decorator(end_val=" ...")
def func3a(value):
    return value


@my_text_decorator2("-=[", end_val=" ...")
def func3b(value):
    return value


@my_text_decorator("|> ", " <|")
def func4a(value):
    return value


@my_text_decorator2("|> ", " <|")
def func4b(value):
    return value


@my_text_decorator(end_val=" ...", start_val="|> ")
def func5a(value):
    return value


@my_text_decorator2("|> ", end_val=" ...")
def func5b(value):
    return value


print(func1a('My sample text'))  # func1a -=[My sample text]=-
print(func2a('My sample text'))  # func2a -=[My sample text]=-
print(func2b('My sample text'))  # func2b -=[My sample text]=-
print(func3a('My sample text'))  # func3a -=[My sample text ...
print(func3b('My sample text'))  # func3b -=[My sample text ...
print(func4a('My sample text'))  # func4a |> My sample text <|
print(func4b('My sample text'))  # func4b |> My sample text <|
print(func5a('My sample text'))  # func5a |> My sample text ...
print(func5b('My sample text'))  # func5b |> My sample text ...

Note: it has the same drawback where you can't pass 1 argument as function to decorator, but if you want this functionality on multiple decorators you can evade the code boilerplate.注意:它有同样的缺点,你不能将 1 个参数作为函数传递给装饰器,但是如果你想在多个装饰器上使用这个功能,你可以避开代码样板。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM