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

前端 未结 13 1612
死守一世寂寞
死守一世寂寞 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:34

    In fact, the caveat case in @bj0's solution can be checked easily:

    def meta_wrap(decor):
        @functools.wraps(decor)
        def new_decor(*args, **kwargs):
            if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
                # this is the double-decorated f. 
                # Its first argument should not be a callable
                doubled_f = decor(args[0])
                @functools.wraps(doubled_f)
                def checked_doubled_f(*f_args, **f_kwargs):
                    if callable(f_args[0]):
                        raise ValueError('meta_wrap failure: '
                                    'first positional argument cannot be callable.')
                    return doubled_f(*f_args, **f_kwargs)
                return checked_doubled_f 
            else:
                # decorator arguments
                return lambda real_f: decor(real_f, *args, **kwargs)
    
        return new_decor
    

    Here are a few test cases for this fail-safe version of meta_wrap.

        @meta_wrap
        def baddecor(f, caller=lambda x: -1*x):
            @functools.wraps(f)
            def _f(*args, **kwargs):
                return caller(f(args[0]))
            return _f
    
        @baddecor  # used without arg: no problem
        def f_call1(x):
            return x + 1
        assert f_call1(5) == -6
    
        @baddecor(lambda x : 2*x) # bad case
        def f_call2(x):
            return x + 1
        f_call2(5)  # raises ValueError
    
        # explicit keyword: no problem
        @baddecor(caller=lambda x : 100*x)
        def f_call3(x):
            return x + 1
        assert f_call3(5) == 600
    

提交回复
热议问题