Search code examples
pythondecoratorpython-decorators

Transparently chain (combine) decorators


I know this is not how decorators are intended but..

I have decorators to save various outpust from functions to xlsx, csv, other

what I want to do is

@save_xlsx
@save_csv
def whatever_function():

and have one file saved in .xlsx and the other in .csv, both working with the same output from whatever_function(). I also want to be able to use only one decorator on other functions.

Or maybe this is not the right approach?


Solution

  • This kinda reminds me of monads. Anyway, you can make a transparent generic logging decorator:

    def log(callback):
        def logger(fn):
            def logwrapper(*args, **kwargs):
                value = fn(*args, **kwargs)
                callback(value)
                return value
            return logwrapper
        return logger
    
    csv_callback = lambda x: print('writing {} to a csv file'.format(x))
    xlsx_callback = lambda x: print('writing {} to an xlsx file'.format(x))
    
    @log(csv_callback)
    @log(xlsx_callback)
    def test(a, b):
        return a + b
    
    In [2]: test(1, 2)
    writing 3 to an xlsx file
    writing 3 to a csv file
    Out[2]: 3
    
    In [3]: test(2, 3)
    writing 5 to an xlsx file
    writing 5 to a csv file
    Out[3]: 5
    

    You can also just pass several callbacks into the logger to avoid excessive nesting

    def log(*callbacks):
        def logger(fn):
            def logwrapper(*args, **kwargs):
                value = fn(*args, **kwargs)
                for callback in callbacks:
                    callback(value)
                return value
            return logwrapper
        return logger
    
    @log(csv_callback, xlsx_callback)
    def test(a, b):
        return a + b