Categories
decorator python python-decorators

How to make function decorators and chain them together?

3039

How can I make two decorators in Python that would do the following?

@make_bold
@make_italic
def say():
   return "Hello"

…which should return:

"<b><i>Hello</i></b>"

0

    3053

    Check out the documentation to see how decorators work. Here is what you asked for:

    from functools import wraps
    
    def makebold(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return "<b>" + fn(*args, **kwargs) + "</b>"
        return wrapper
    
    def makeitalic(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return "<i>" + fn(*args, **kwargs) + "</i>"
        return wrapper
    
    @makebold
    @makeitalic
    def hello():
        return "hello world"
    
    @makebold
    @makeitalic
    def log(s):
        return s
    
    print hello()        # returns "<b><i>hello world</i></b>"
    print hello.__name__ # with functools.wraps() this returns "hello"
    print log('hello')   # returns "<b><i>hello</i></b>"
    

    3

    • 271

      Consider using functools.wraps or, better yet, the decorator module from PyPI: they preserve certain important metadata (such as __name__ and, speaking about the decorator package, function signature).

      Mar 11, 2011 at 2:30


    • 34

      *argsand **kwargs should be added in the answer. Decorated function can have arguments, and they will be lost if not specified.

      – Blusky

      Apr 2, 2017 at 11:47

    • 4

      Although this answer has the great advantage of only using the stdlib, and works for this simple example where there are no decorator arguments nor decorated function arguments, it has 3 major limitations: (1) no simple support for optional decorator arguments (2) not signature-preserving (3) no simple way to extract a named argument from *args, **kwargs. An easy way to solve these 3 issues at once is to use decopatch as explained here. You can also use decorator as already mentioned by Marius Gedminas, to solve points 2 and 3.

      – smarie

      Mar 11, 2019 at 15:28


    3053

    Check out the documentation to see how decorators work. Here is what you asked for:

    from functools import wraps
    
    def makebold(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return "<b>" + fn(*args, **kwargs) + "</b>"
        return wrapper
    
    def makeitalic(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return "<i>" + fn(*args, **kwargs) + "</i>"
        return wrapper
    
    @makebold
    @makeitalic
    def hello():
        return "hello world"
    
    @makebold
    @makeitalic
    def log(s):
        return s
    
    print hello()        # returns "<b><i>hello world</i></b>"
    print hello.__name__ # with functools.wraps() this returns "hello"
    print log('hello')   # returns "<b><i>hello</i></b>"
    

    3

    • 271

      Consider using functools.wraps or, better yet, the decorator module from PyPI: they preserve certain important metadata (such as __name__ and, speaking about the decorator package, function signature).

      Mar 11, 2011 at 2:30


    • 34

      *argsand **kwargs should be added in the answer. Decorated function can have arguments, and they will be lost if not specified.

      – Blusky

      Apr 2, 2017 at 11:47

    • 4

      Although this answer has the great advantage of only using the stdlib, and works for this simple example where there are no decorator arguments nor decorated function arguments, it has 3 major limitations: (1) no simple support for optional decorator arguments (2) not signature-preserving (3) no simple way to extract a named argument from *args, **kwargs. An easy way to solve these 3 issues at once is to use decopatch as explained here. You can also use decorator as already mentioned by Marius Gedminas, to solve points 2 and 3.

      – smarie

      Mar 11, 2019 at 15:28


    154

    Alternatively, you could write a factory function which return a decorator which wraps the return value of the decorated function in a tag passed to the factory function. For example:

    from functools import wraps
    
    def wrap_in_tag(tag):
        def factory(func):
            @wraps(func)
            def decorator():
                return '<%(tag)s>%(rv)s</%(tag)s>' % (
                    {'tag': tag, 'rv': func()})
            return decorator
        return factory
    

    This enables you to write:

    @wrap_in_tag('b')
    @wrap_in_tag('i')
    def say():
        return 'hello'
    

    or

    makebold = wrap_in_tag('b')
    makeitalic = wrap_in_tag('i')
    
    @makebold
    @makeitalic
    def say():
        return 'hello'
    

    Personally I would have written the decorator somewhat differently:

    from functools import wraps
    
    def wrap_in_tag(tag):
        def factory(func):
            @wraps(func)
            def decorator(val):
                return func('<%(tag)s>%(val)s</%(tag)s>' %
                            {'tag': tag, 'val': val})
            return decorator
        return factory
    

    which would yield:

    @wrap_in_tag('b')
    @wrap_in_tag('i')
    def say(val):
        return val
    say('hello')
    

    Don’t forget the construction for which decorator syntax is a shorthand:

    say = wrap_in_tag('b')(wrap_in_tag('i')(say)))
    

    1

    • 6

      In my opinion, it is better to avoid more than one decorator as far as possible. If i had to write a factory function i would code it with *kwargs like def wrap_in_tag(*kwargs) then @wrap_in_tag('b','i')

      – guneysus

      Oct 29, 2013 at 22:29