How to create a Python decorator that can be used either with or without parameters?

前端 未结 13 1647
死守一世寂寞
死守一世寂寞 2020-11-30 21:26

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

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

or

13条回答
  •  难免孤独
    2020-11-30 21:41

    A python decorator is called in a fundamentally different way depending on whether you give it arguments or not. The decoration is actually just a (syntactically restricted) expression.

    In your first example:

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

    the function redirect_output is called with the given argument, which is expected to return a decorator function, which itself is called with foo as an argument, which (finally!) is expected to return the final decorated function.

    The equivalent code looks like this:

    def foo():
        ....
    d = redirect_output("somewhere.log")
    foo = d(foo)
    

    The equivalent code for your second example looks like:

    def foo():
        ....
    d = redirect_output
    foo = d(foo)
    

    So you can do what you'd like but not in a totally seamless way:

    import types
    def redirect_output(arg):
        def decorator(file, f):
            def df(*args, **kwargs):
                print 'redirecting to ', file
                return f(*args, **kwargs)
            return df
        if type(arg) is types.FunctionType:
            return decorator(sys.stderr, arg)
        return lambda f: decorator(arg, f)
    

    This should be ok unless you wish to use a function as an argument to your decorator, in which case the decorator will wrongly assume it has no arguments. It will also fail if this decoration is applied to another decoration that does not return a function type.

    An alternative method is just to require that the decorator function is always called, even if it is with no arguments. In this case, your second example would look like this:

    @redirect_output()
    def foo():
        ....
    

    The decorator function code would look like this:

    def redirect_output(file = sys.stderr):
        def decorator(file, f):
            def df(*args, **kwargs):
                print 'redirecting to ', file
                return f(*args, **kwargs)
            return df
        return lambda f: decorator(file, f)
    

提交回复
热议问题