Jim
Jim

Reputation: 14270

How to pass through Python args and kwargs?

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

Answers (4)

Civil
Civil

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

Muhammad Yasirroni
Muhammad Yasirroni

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

unutbu
unutbu

Reputation: 879143

The * and ** operators are used in two different situations.

  1. 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.

  2. 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

Martijn Pieters
Martijn Pieters

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

Related Questions