Just make it a precondition for decorating a function that your function will receive a logger as an argument: it's just not something the result will take. (This is similar to how mock.patch
works as a decorator.)
def logged(func):
logger = ...
@wraps(func)
def _logged(*args, **kwargs):
return func(*args, logger=logger, **kwargs):
return _logged
@logged
def foo(x, y, *, logger):
...
foo(3, 5)
Despite appearances, logger
is not a required keyword-only argument, because the name foo
does not refer to your 3-argument function anymore. It refers to the function that logged
creates, which will take care of passing the logger to your original foo
when it finally gets called.
If you don't like having logger
appear to be required, you can give it any default value you like, because that default value will never be used.
@logged
def foo(x, y, *, logger=None):
...
Note that while _logged
is a closure, func
is not, because closures deal with lexical scopes, and func
was not defined in a lexical scope where logger
was defined.