Reputation: 21
Can you explain to me how the partial function is working here?
def debug(func=None, *, prefix=''):
if func is None:
return partial(debug, prefix=prefix)
msg = prefix + func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
print(msg)
return func(*args, **kwargs)
return wrapper
Upvotes: 1
Views: 75
Reputation: 104712
The debug
function is designed so that it can optionally be called with the prefix
keyword-only argument if you want, when you apply it as a decorator:
@debug(prefix="foo"): # calling debug with a prefix works
def foo():
pass
@debug # skipping the call also works
def bar():
pass
The way it does this is by using functools.partial
. The partial
function returns a callable argument that behaves just like the function you pass it, but it will add in the extra arguments you pass to partial
whenever the partial objects gets called itself.
Here's an example:
from functools import partial
def foo(arg1, arg2):
print(arg1, arg2)
foo("hi", "there") # prints "hi there"
bar = partial(foo, "bar")
bar("quux") # prints "bar quux"
baz = partial(foo, arg2="baz")
baz("hmm") # prints "hmm baz"
The decorator syntax using @decorator
passes the function that is defined on the next line as a single argument to the decorator. But if you call something on the decorator line, the function gets passed to the return value of the call instead. So:
@debug
def foo():
pass
Is equivalent to:
def foo():
pass
foo = debug(foo)
Whereas:
@debug(prefix="foo")
def foo():
pass
Is equivalent to:
deco = debug(prefix="foo") # this is partial(debug, prefix="foo")
@deco
def foo():
pass
Which thanks to partial
is equivalent to:
def foo():
pass
foo = debug(foo, prefix="foo") # passing both arguments here, that's not normally possible!
Upvotes: 1