Can I combine two decorators into a single one in Python?

前端 未结 6 592
刺人心
刺人心 2020-11-29 02:08

Is there a way to combine two decorators into one new decorator in python?

I realize I can just apply multiple decorators to a function, but I was curious as to whet

6条回答
  •  挽巷
    挽巷 (楼主)
    2020-11-29 02:32

    Decorators are just functions that take a function as input and return a new function. This:

    @deco
    def foo():
        ...
    

    Is equivalent to this:

    def foo():
        ...
    
    foo = deco(foo)
    

    In other words, the decorated function (foo) is passed as an argument to the decorator, and then foo is replaced with the return value of the decorator. Equipped with this knowledge, it's easy to write a decorator that combines two other decorators:

    def merged_decorator(func):
        return decorator2(decorator1(func))
    
    # now both of these function definitions are equivalent:
    
    @decorator2
    @decorator1
    def foo():
        ...
    
    @merged_decorator
    def foo():
        ...
    

    It gets a little trickier if the decorators accept arguments, like these two:

    @deco_with_args2(bar='bar')
    @deco_with_args1('baz')
    def foo():
        ...
    

    You might wonder how these decorators are even implemented. It's actually pretty simple: deco_with_args1 and deco_with_args2 are functions that return another function decorator. Decorators with arguments are essentially decorator factories. The equivalent of this:

    @deco_with_args('baz')
    def foo():
        ...
    

    Is this:

    def foo():
        ...
    
    real_decorator = deco_with_args('baz')
    foo = real_decorator(foo)
    

    In order to make a decorator that accepts arguments and then applies two other decorators, we have to implement our own decorator factory:

    def merged_decorator_with_args(bar, baz):
        # pass the arguments to the decorator factories and
        # obtain the actual decorators
        deco2 = deco_with_args2(bar=bar)
        deco1 = deco_with_args1(baz)
    
        # create a function decorator that applies the two
        # decorators we just created
        def real_decorator(func):
            return deco2(deco1(func))
    
        return real_decorator
    

    This decorator can then be used like this:

    @merged_decorator_with_args('bar', 'baz')
    def foo():
        ...
    

提交回复
热议问题