I am attempting to understand a piece of code that involves a bit of use of the **kwargs when a function gets called. After reading through the Pydocs and some other posts on Stackoverflow, I attempted to experiment with this syntax interactively and encountered some cases where I could not understand the behavior:
In [180]: d1 = {'a':'b', 'c':'d'}
In [181]: d2 = {'e':'f', 'g':'h', 'i':'j'}
In [182]: dict(d1, d2)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-182-49f16d5922b6> in <module>()
----> 1 dict(d1, d2)
TypeError: dict expected at most 1 arguments, got 2
In [183]: dict(d1, **d2)
Out[183]: {'a': 'b', 'c': 'd', 'e': 'f', 'g': 'h', 'i': 'j'}
In [184]: dict(d1, *d2)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-184-0d16a1c0b887> in <module>()
----> 1 dict(d1, *d2)
TypeError: dict expected at most 1 arguments, got 4
In [185]: dict(*d1,d2)
File "<ipython-input-185-d36e77e65d5e>", line 1
dict(*d1,d2)
SyntaxError: only named arguments may follow *expression
In [186]: dict(**d1,d2)
File "<ipython-input-186-fd4b93fcc3c9>", line 1
dict(**d1,d2)
^
SyntaxError: invalid syntax
In the code that I am currently studying, I see the syntax of In [183] being used and from what currently I understand of **d2 when it is used as such is that it expands the dictionary object d2 in place into a tuple of that looks like (e=f, g=h, i=j) so the statement for In [183] could look like (I'm not sure which one, but I tired all possibilities I could think of and none worked which puzzles me):
In [192]: dict({'a':'b', 'c':'d'}, (e=f, g=h, i=j))
File "<ipython-input-192-792b64fb793a>", line 1
dict({'a':'b', 'c':'d'}, (e=f, g=h, i=j))
^
SyntaxError: invalid syntax
for this above, I thought the brackets enclosing e=f, g=h, i=j might be the problem because this works:
In [191]: dict(a=5,l=6)
Out[191]: {'a': 5, 'l': 6}
but when I removed them, I get:
In [196]: dict({'a':'b', 'c':'d'}, e=f, g=h, i=j)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-196-854a7d483a98> in <module>()
----> 1 dict({'a':'b', 'c':'d'}, e=f, g=h, i=j)
NameError: name 'f' is not defined
and trying just the e=f, g=h, i=j portion alone does not work:
In [197]: dict(e=f, g=h, i=j)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-197-b53d153dfd98> in <module>()
----> 1 dict(e=f, g=h, i=j)
NameError: name 'f' is not defined
and then I realized that putting the '' around f, h and i solved the problem:
In [199]: dict(e='f', g='h', i='j')
Out[199]: {'e': 'f', 'g': 'h', 'i': 'j'}
So I guess what I found here was that the right hand side of the key=value pair does not need to be defined as a string or perhaps any object type for that matter, but why is this so?
and when I went back to the original issue, I had
In [200]: dict({'a':'b', 'c':'d'}, e='f', g='h', i='j')
Out[200]: {'a': 'b', 'c': 'd', 'e': 'f', 'g': 'h', 'i': 'j'}
And so it finally worked!
Sorry if I have been not clear on what my issues are here, so to just summarize, I will list out all my questions on this:
How is it that when I did
In [183]: dict(d1, **d2)it worked and I did not get any errors relating to the number of arguments, whereas forIn [182]: dict(d1, d2)andIn [184]: dict(d1, *d2)I got errors telling me that I have put in more arguments todictthan what it accepts? How didd1, **d2turn into a single argument?From the errors in doing
In [185]: dict(*d1,d2)andIn [186]: dict(**d1,d2), it seems clear the having*or**to the first dictionary object does not work. Why can't this be done? For example, why doesn'tdict(**d1, d2)do the same thing asdict(d1, **d2)?Somewhat related to Q1 - from my understanding of what
**kwargsdoes, it somehow expands the dictionary out into itskey = valueand when applied to me example, probably gave me something likeIn [200]: dict({'a':'b', 'c':'d'}, e='f', g='h', i='j')as it ran correctly. But doesn't this look like there were 4 arguments? Why didn't this throw an error?My last question is why when using the
key = valuepair syntax as arguments intodict, we do not need to enclose thekeyin any quotes, but only do it for thevalue, despite the key itself being astringobject too as is evident in the outputs from the successfuldictconstructions?
Thank you all very much for your help, and I will appreciate any comments or suggestions here too that may not be directly related to my problems here. This behavior just seems so obscure to me, and I can't understand it!
The behavior isn't obscure at all if you read the docs (https://docs.python.org/2/library/stdtypes.html#typesmapping)
Remember that when you call
dict(), you are calling a function. Functions have a defined number of parameters, and these parameters have a defined order.There are three definitions of
dict(), namely:It's simply just how the language was designed. This should answer your questions in (1) and (2).
EDIT: For (3), you're right in thinking that it expands the key/value pairs out.
**kwargsisnnumber of named parameters, so when you passi=1, j=2, k=3you are passing 3 named parameters. If a function is defined with**kwargsas a param, that means it can receive0 to nnamed parameters. There's a distinction between a normal param and a named param.For (4), this is just a compiler trick. It's there for convenience I guess, but in practice it is rarely used. I personally have yet to see the
key=valueconvention used in the "real world". YMMV.