如何创建可以使用或不使用参数的Python装饰器? [英] How to create a Python decorator that can be used either with or without parameters?

查看:75
本文介绍了如何创建可以使用或不使用参数的Python装饰器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个可以与参数一起使用的Python装饰器:

  @redirect_output(在某处。 log)
def foo():
....

或没有它们(例如,默认将输出重定向到stderr):

  @redirect_output 
def foo():
....

这是否有可能?



请注意,我不是在寻找重定向输出问题的其他解决方案,它只是我想要实现的语法的一个示例。

解决方案

我知道这个问题很旧,但是有些评论是新的,尽管所有可行的解决方案本质上都是相同的,但大多数都不是很干净

就像thobe的回答说的那样,处理这两种情况的唯一方法是检查这两种情况。最简单的方法就是简单地检查是否有单个参数并且它是马蹄莲(注意:如果您的装饰器仅接受1个参数并且恰好是可调用对象,则需要额外检查):

  def装饰器(* args,** kwargs):
如果len(args)== 1和len(kwargs)== 0并且callable(args [0]):
#称为@decorator
其他:
#称为@decorator(* args,** kwargs)

在第一种情况下,您可以执行任何普通装饰器的操作,返回传入函数的修改或包装版本。



在第二种情况下,您返回一个新修饰符,该修饰符以某种方式使用通过* args,** kwargs传递的信息。



这一切都很好,但是必须为您制作的每个装饰器将其写出来,这会很烦人,而且不够干净。取而代之的是,能够自动修改我们的装饰器而不必重新编写它们,这将是很好的……但这就是装饰器的作用!



使用以下装饰器装饰器,我们可以对装饰器进行装饰,以便它们可以使用或不使用参数:

  def doublewrap(f):
'''
a装饰器装饰器,允许该装饰器用作:
@decorator(with,arguments, and = kwargs)

@decorator
'''
@wraps(f)
def new_dec(* args,** kwargs):
如果len(args)== 1和len(kwargs)== 0且可调用(args [0]):
#实际修饰函数
返回f(args [0])
else :
#装饰器参数
返回lambda realf:f(realf,* args,** kwargs)

return new_dec

现在,我们可以用@doublewrap装饰我们的装饰器,它们可以使用和不使用参数,但有一个警告:



我在上面提到过,但是应该在这里重复,此装饰器中的检查对arg进行了假设装饰者可以接收的提示(即,它不能接收单个可调用的参数)。由于我们现在使它适用于任何生成器,因此需要牢记或修改它,以防矛盾。



以下内容演示了其用法:

  def test_doublewrap():util导入
doublewrap
来自functools导入包装

@doublewrap
def mult(f,factor = 2):
'''乘以函数的返回值''
@wraps(f)
def wrap(* args ,** kwargs):
返回因子* f(* args,** kwargs)
换行

#尝试正常
@mult
def f(x,y):
返回x + y

#尝试args
@mult(3)
def f2(x,y):
return x * y

#试试kwargs
@mult(factor = 5)
def f3(x,y):
return x-y

断言f(2,3)== 10
断言f2(2,5)== 30
断言f3(8,1)== 5 * 7


I'd like to create a Python decorator that can be used either with parameters:

@redirect_output("somewhere.log")
def foo():
    ....

or without them (for instance to redirect the output to stderr by default):

@redirect_output
def foo():
    ....

Is that at all possible?

Note that I'm not looking for a different solution to the problem of redirecting output, it's just an example of the syntax I'd like to achieve.

解决方案

I know this question is old, but some of the comments are new, and while all of the viable solutions are essentially the same, most of them aren't very clean or easy to read.

Like thobe's answer says, the only way to handle both cases is to check for both scenarios. The easiest way is simply to check to see if there is a single argument and it is callabe (NOTE: extra checks will be necessary if your decorator only takes 1 argument and it happens to be a callable object):

def decorator(*args, **kwargs):
    if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
        # called as @decorator
    else:
        # called as @decorator(*args, **kwargs)

In the first case, you do what any normal decorator does, return a modified or wrapped version of the passed in function.

In the second case, you return a 'new' decorator that somehow uses the information passed in with *args, **kwargs.

This is fine and all, but having to write it out for every decorator you make can be pretty annoying and not as clean. Instead, it would be nice to be able to automagically modify our decorators without having to re-write them... but that's what decorators are for!

Using the following decorator decorator, we can deocrate our decorators so that they can be used with or without arguments:

def doublewrap(f):
    '''
    a decorator decorator, allowing the decorator to be used as:
    @decorator(with, arguments, and=kwargs)
    or
    @decorator
    '''
    @wraps(f)
    def new_dec(*args, **kwargs):
        if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
            # actual decorated function
            return f(args[0])
        else:
            # decorator arguments
            return lambda realf: f(realf, *args, **kwargs)

    return new_dec

Now, we can decorate our decorators with @doublewrap, and they will work with and without arguments, with one caveat:

I noted above but should repeat here, the check in this decorator makes an assumption about the arguments that a decorator can receive (namely that it can't receive a single, callable argument). Since we are making it applicable to any generator now, it needs to be kept in mind, or modified if it will be contradicted.

The following demonstrates its use:

def test_doublewrap():
    from util import doublewrap
    from functools import wraps    

    @doublewrap
    def mult(f, factor=2):
        '''multiply a function's return value'''
        @wraps(f)
        def wrap(*args, **kwargs):
            return factor*f(*args,**kwargs)
        return wrap

    # try normal
    @mult
    def f(x, y):
        return x + y

    # try args
    @mult(3)
    def f2(x, y):
        return x*y

    # try kwargs
    @mult(factor=5)
    def f3(x, y):
        return x - y

    assert f(2,3) == 10
    assert f2(2,5) == 30
    assert f3(8,1) == 5*7

这篇关于如何创建可以使用或不使用参数的Python装饰器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆