28

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?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jim
  • 13,430
  • 26
  • 104
  • 155
  • See also [Expanding tuples into arguments](https://stackoverflow.com/questions/1993727/expanding-tuples-into-arguments) for just the argument side, and [What does ** (double star/asterisk) and * (star/asterisk) do for parameters?](https://stackoverflow.com/questions/36901) for the parameter side. – Karl Knechtel Aug 20 '22 at 01:14

3 Answers3

44

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
20

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • In case anyone's interested, I just ran across this article that describes in more detail the answer that Martijn gives below: https://hangar.runway7.net/python/packing-unpacking-arguments – Jim May 20 '14 at 00:06
  • I'm wondering, why they are doing it differently in Python logging code: https://i.imgur.com/pTyL5CI.png - it's missing `*` before `args` when it calls `self_log` – JustAMartin May 09 '20 at 22:18
  • @JustAMartin because the [`Logger._log()` method](https://github.com/python/cpython/blob/2c3d508c5fabe40dac848fb9ae558069f0576879/Lib/logging/__init__.py#L1553-L1554) expects values to interpolate into the logged message as a sequence, a single argument. It doesn’t want those as separate arguments. The `method(*args, **kwargs)` pattern doesn’t apply there because there is no intent to shadow the method signature, `Logger._log()` is an internal method, the public `Logger.info()` doesn’t need to pretend it accepts the same arguments. – Martijn Pieters May 10 '20 at 01:39
-2

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.

Muhammad Yasirroni
  • 1,512
  • 12
  • 22
  • 1
    For those that down-voting this answer, can you give some explanation so that I will not doing `the wrong things` in the future? That is because I got my reputation answering old question like this, providing examples that help others. I'm learning Python, searching SO when confused, improving answer when I'm still in trouble after reading answers. This also documents my journey learning Python while helping others like me. – Muhammad Yasirroni Aug 27 '23 at 08:59