Reputation: 37
I'm trying to write a function decorator, that prints arguments, with which a function was called and I noticed one thing. If we create decorator, that simply prints kwargs, it will work. So
def counted(fn):
def wrapper(*args, **kwargs):
print kwargs
return fn(*args, **kwargs)
return wrapper
@counted
def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
return
foo(a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
outputs
{'a': 1, 'c': 3, 'b': 2, 'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
However, if the function is called without decorator, but the arguments are printed inside the function, output differs:
#@counted
def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
print kwargs #added this line
return
Output:
{'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
Why is that? Why does print kwargs in a decorator prints first three arguments, but don't in a function?
Upvotes: 1
Views: 275
Reputation: 1121972
kwargs
captures any key=value
parameter that wasn't specifically named in your function signature. foo
names a
, b
and c
specifically, so those are not captured, but your decorator wrapper names none, so everything is captured in kwargs
. Note that this includes the args=...
and kwargs=...
parameters you passed into your call.
You need to add print
to both places and you'll see it is not the decorator that is doing anything special here:
>>> @counted
... def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
... print kwargs
...
>>> foo(a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
{'a': 1, 'c': 3, 'b': 2, 'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
{'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
You also don't print args
, so you don't see what positional arguments are passed along. Let's add some more print statements to show what is really going on:
def counted(fn):
def wrapper(*args, **kwargs):
print 'counted args: {!r}'.format(args)
print 'counted kwargs: {!r}'.format(kwargs)
return fn(*args, **kwargs)
return wrapper
@counted
def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
print 'foo a, b, c: {!r}, {!r}, {!r}'.format(a, b, c)
print 'foo args: {!r}'.format(args)
print 'foo kwargs: {!r}'.format(kwargs)
Running this you'll get a much better picture:
>>> foo(a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
counted args: ()
counted kwargs: {'a': 1, 'c': 3, 'b': 2, 'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
foo a, b, c: 1, 2, 3
foo args: ()
foo kwargs: {'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
Again, note that both 'args'
and 'kwargs'
are themselves keyword arguments; they both end up as keys in the kwargs
dictionary. You could name them anything else and they'll still end up with the new names:
>>> foo(a=1, b=2, c=3, spam=[4, 5], eggs={'d': 6, 'g': 12.9})
counted args: ()
counted kwargs: {'a': 1, 'c': 3, 'b': 2, 'eggs': {'d': 6, 'g': 12.9}, 'spam': [4, 5]}
foo a, b, c: 1, 2, 3
foo args: ()
foo kwargs: {'eggs': {'d': 6, 'g': 12.9}, 'spam': [4, 5]}
You didn't specify values for the *args
and **kwargs
catch-all variables there, you specified more arbitrary keyword arguments to be captured in kwargs
.
The remaining arguments, a
, b
and c
are 'captured' by the named parameters of the foo
function. You defined those separately in foo
so they are assigned to specifically. They are not part of the wrapper()
definition, so they couldn't be captured there.
Note that you didn't pass any positional parameters to the call, so the *args
variable is empty. Pass in some positional arguments instead:
>>> foo(1, 2, 3, 42, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
counted args: (1, 2, 3, 42)
counted kwargs: {'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
foo a, b, c: 1, 2, 3
foo args: (42,)
foo kwargs: {'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
Now the first 3 values end up in a
, b
and c
still, but the extra positional value 42
is still available in args
in foo
. In the wrapper, where there were no explicitly named arguments, they all ended up in *args
.
If you expected your args=[..]
and kwargs={...}
values to be applied as separate arguments, you need to use the same *
and **
prefixes when calling without using the capturing names:
>>> foo(1, 2, 3, 42, *[4, 5], **{'d': 6, 'g': 12.9})
counted args: (1, 2, 3, 42, 4, 5)
counted kwargs: {'d': 6, 'g': 12.9}
foo a, b, c: 1, 2, 3
foo args: (42, 4, 5)
foo kwargs: {'d': 6, 'g': 12.9}
Now 4
and 5
also appear in args
, and the 'd'
and 'g'
keys appear in the kwargs
dictionaries directly.
Upvotes: 1