Search code examples
pythondecorator

Way to extend context of function? for example in decorator


As for example if want let decorated function see logger from closure

def logged(func):
    #here i want create logger which i want to be available from decorated function.
    @wraps(func)
    def _logged(*args, **kwargs):
        
        return func(*args,**kwargs)
        #naive idea hot to do it - obviously doesn't work
        #return exec('func(*args,**kwargs)',dict(func=func,logger=logger,args=args,kwargs=kwargs),dict(func=func,logger=logger,args=args,kwargs=kwargs))
    return _logged

Solution

  • Here's (another) way to do it, but again it's not through a closure which are determined at compile time, so can't be added at runtime. This version adds a global variable to the function's global namespace, taking care to save and restore the value of any similarly named variable that was already there. I got the idea from @Martijn Pieters' answer to the somewhat related question: How to inject variable into scope with a decorator?

    from functools import wraps
    
    
    class Logger:
        def __call__(self, *args, **kwargs):
            return print(*args, **kwargs)
    
    
    def logged(func):
        logger = Logger()  # Get or create value to be added.
        varname = 'logger'
        sentinel = object()
    
        @wraps(func)
        def _logged(*args, **kwargs):
            namespace = func.__globals__
            oldvalue = namespace.get(varname, sentinel)  # Save prev value.
            namespace[varname] = logger
    
            try:
                return func(*args, **kwargs)
            finally:
                if oldvalue is sentinel:
                    del namespace[varname]
                else:
                    namespace[varname] = oldvalue
    
        return _logged
    
    
    @logged
    def myfunc():
        logger('Logged from myfunc.')
    
    
    myfunc()  # -> Logged from myfunc.