4

Why does {'kwargs':{'1':'a', '2':'b'}} appear when I run test_func()? I would have expected just this to print: {'1':'a', '2':'b'}.

Code:

class MyClass:
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def test_func(self):
        print(self.kwargs)

test_kwargs = {'1':'a', '2':'b'}
my_class = MyClass(kwargs=test_kwargs)
my_class.test_func()

Ouput:

{'kwargs': {'1': 'a', '2': 'b'}}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
narnia649
  • 381
  • 2
  • 10
  • 1
    Does this answer your question? [Passing a list of kwargs?](https://stackoverflow.com/questions/1496346/passing-a-list-of-kwargs) Use `my_class = MyClass(**test_kwargs)`. – John Kugelman Aug 09 '21 at 20:52
  • 1
    You would get your expected result if you defined `def __init__(self, kwargs):` without `**`. – Barmar Aug 09 '21 at 20:53
  • @paxdiablo Why did you reopen this? The OP says the duplicate answers their question. – John Kugelman Aug 09 '21 at 21:14
  • @JohnKugelman: I voted to reopen because the question itself is not a duplicate of the one proposed. The other question was how to pass on a `kwargs` to another function, this one was seeking information as to why there was an extra level of indirection involved when doing it this way. Related, yes, but not a duplicate in my opinion - the text for duplicate closing is "This question has been asked before and already has an answer". – paxdiablo Aug 09 '21 at 23:31

3 Answers3

7

It's because you initialize the instance by passing 1 keyword argument named kwargs with the dictionary as value.

If you want to see the dictionary as kwargs, you need to call in using my_class = MyClass(**test_kwargs)

DevLounge
  • 8,313
  • 3
  • 31
  • 44
2

It's because **kwargs automagically collects the remaining (unused) named arguments and puts them in a dictionary for you, much the same as *args collects the unused unnamed (positional) arguments into a tuple(a).

It's meant to handle things like:

my_class = MyClass(a=1, b=2)

which is possibly what you were trying to do.

The way you've done it in the question will result in a dictionary with the key kwargs referencing the dictionary because it will create a single dictionary keyed on the argument name kwargs - that is exactly what you see.


(a) You can see this behaviour in the following transcript:

Python 3.9.0 (default, Oct 12 2020, 02:44:01)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def fn(one, two, *args, **kwargs):
...     print(one)
...     print(two)
...     print(args)
...     print(kwargs)
...
>>> fn(1, 2, 3, 4, 5, six=6, seven=7)
1
2
(3, 4, 5)
{'six': 6, 'seven': 7}

Keep in mind args and kwargs are conventions, not rules. You can use different names, it's the * and ** that matter.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
0

First, let's simplify your example. Since your question has nothing to do with classes, we can use a global function instead:

def test_kwargs(**kwargs):
    print(kwargs)

Second, let's call this with a different name:

test_kwargs = {'1':'a', '2':'b'}
test_kwargs(test=test_kwargs)

Output:

{'test': {'1': 'a', '2': 'b'}}

Now we see that the name test comes from the name given in the function call not the function definition. This is what happened in your original code.

Here are some other examples that might help clarify some things:

You can pass multiple arguments each with their own name:

test_kwargs(a=1, b=2)

Output:

{'a': 1, 'b': 2}

Or you can expand a dictionary as keyword arguments to a function:

test_kwargs(**test_kwargs)

Output:

{'1':'a', '2':'b'}
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268