Reputation: 14270
While I have a general understanding (I think) of Python's *args and **kwargs, I'm having trouble understanding how to pass them from one function through to another. Here's my model:
from pdb import set_trace as debug
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=30)
def __unicode__(self):
return u'%s' % self.name
def save_name_for(self, *args, **kwargs):
self.name = 'Alex'
return self
def save_name(self, *args, **kwargs):
debug()
self.save_name_for(self, args, kwargs)
self.save()
I've split saving a name into two functions above. This way I can unit-test the logic I would normally put all in the save_name method by unit-testing the save_name_for method instead.
When I run this in the interpreter and stop in the save_name method, as I would expect, I see this:
(Pdb) args
self =
args = (1, 2)
kwargs = {'last': 'Doe', 'first': 'John'}
If I then step into the save_name_for method, I see this:
(Pdb) args
self =
args = (<Person: >, (1, 2), {'last': 'Doe', 'first': 'John'})
kwargs =
Is there some way to pass the kwargs that are received by the save_name method directly into save_name_for method so that they appear in the latter's kwargs? I'd like to see something like this in save_name_for method's name space:
(Pdb) args
self =
args = (1, 2)
kwargs = {'last': 'Doe', 'first': 'John'} # <= I want this
I realize I could parse them in save_name and then pass them explicitly to save_name_for but that seems rather inelegant. I also thought I might do this since args is a tuple...
kwargs = args[2]
... but it doesn't appear to work. args[2] is just everything (I don't understand this). Is there a Pythonic way to do this?
Upvotes: 37
Views: 42487
Reputation: 79
Python 3.12.2
def f(a,b,*value_list,c,**key_value_dict):
print(a,b,value_list,c,key_value_dict)
x={'a':1,'b':2,'c':3,'d':4}
f(**x) # 1 2 () 3 {'d': 4}
f(*x) # TypeError: f() missing 1 required keyword-only argument: 'c'
f(*x,c=4) # a b ('c', 'd') 4 {}
y=(1,2,3)
f(*y) # TypeError: f() missing 1 required keyword-only argument: 'c'
f(*y,c=4) # 1 2 (3,) 4 {}
f(*y,**x) # TypeError: f() got multiple values for argument 'a'
f(1,2,5,6,d=7,c=4,e=8) # 1 2 (5, 6) 4 {'d': 7, 'e': 8}
f(1, 2, *(5, 6), c=4, **{'d': 7, 'e': 8}) # 1 2 (5, 6) 4 {'d': 7, 'e': 8}
f(*(),1,*(),2,*(),**{},c=4,**{}) # 1 2 () 4 {}
Upvotes: -1
Reputation: 2137
This example will clear your confusion of **arg
and **kwargs
that appears when you are developing a function based on another function.
def square_area(l, w):
print(f'(l, w): {l, w}')
return l * w
def rectangular_prism_volume(h, *args, **kwargs):
print(f'h: {h}')
return h * square_area(*args, **kwargs)
print(rectangular_prism_volume(1, 2, 3))
print(rectangular_prism_volume(w=1, l=2, h=3))
print(rectangular_prism_volume(1, l=2, w=3))
print(rectangular_prism_volume(1, 2, w=3))
try:
print(rectangular_prism_volume(1, 2, l=3))
except TypeError as e:
print(e)
pass
Outputs:
>>> print(rectangular_prism_volume(1, 2, 3))
h: 1
(l, w): (2, 3)
6
>>> print(rectangular_prism_volume(w=1, l=2, h=3))
h: 3
(l, w): (2, 1)
6
>>> print(rectangular_prism_volume(1, l=2, w=3))
h: 1
(l, w): (2, 3)
6
>>> print(rectangular_prism_volume(1, 2, w=3))
h: 1
(l, w): (2, 3)
6
>>>
>>> try:
... print(rectangular_prism_volume(1, 2, l=3))
... except TypeError as e:
... print(e)
... pass
...
h: 1
square_area() got multiple values for argument 'l'
Based on that example, others answer already answer your question, that is using self.save_name_for(*args, **kwargs)
. Thus, if you design a function that use another function inside it, and want to let user pass the arguments, make sute to add *args
and **kwargs
as input and pass it to the other.
This approach is useful if you want for example, make a wrapper to plot something that call matplotlib.pyplot
inside it. Your wrapper will handle most of the works, but also let user control the plot a bit.
Upvotes: -2
Reputation: 879143
The *
and **
operators are used in two different situations.
When used as part of a function definition,
def save_name_for(self, *args, **kwargs):
it is used to signify an arbitrary number of positional or keyword
arguments, respectively. The point to remember is that inside the
function args
will be a tuple, and kwargs
will be a
dict.
When used as part of a function call,
args = (1, 2)
kwargs = {'last': 'Doe', 'first': 'John'}
self.save_name_for(*args, **kwargs)
the *
and **
act as unpacking operators. args
must be an
iterable, and kwargs
must be dict-like. The items in args
will be unpacked and sent to the function as positional arguments,
and the key/value pairs in kwargs
will be sent to the function as
keyword arguments. Thus,
self.save_name_for(*args, **kwargs)
is equivalent to
self.save_name_for(1, 2, last='Doe', first='John')
See also the saltycrane blog for an explanation with examples.
Upvotes: 53
Reputation: 1121406
You pass them with syntax mirroring the argument syntax:
self.save_name_for(*args, **kwargs)
Note that you do not need to pass in self
; save_name_for
is already bound.
Upvotes: 24