7

Can anyone explain the difference when unpacking the dictionary using a single or a double asterisk? You can mention their difference when used in function parameters, only if it is relevant here, which I don't think so.

However, there may be some relevance, because they share the same asterisk syntax.

def foo(a,b)
    return a+b

tmp = {1:2,3:4}
foo(*tmp)        #you get 4
foo(**tmp)       #typeError: keyword should be string. Why it bothers to check the type of keyword? 

Besides, why the key of dictionary is not allowed to be non-string when passed as function arguments in THIS situation? Are there any exceptions? Why they design Python in this way, is it because the compiler can't deduce the types in here or something?

mirekphd
  • 4,799
  • 3
  • 38
  • 59
Han XIAO
  • 1,148
  • 9
  • 20
  • 1
    Single \* unpacking grabs the keys, so it's adding 1 and 3. With double \*\* it's trying to call `foo(1=2, 3=4)` which doesn't make any sense. Keyword arguments must be a valid [identifier](https://docs.python.org/3/reference/lexical_analysis.html#identifiers). – alkasm Nov 05 '18 at 03:55
  • Responding to "Why it bothers to check the type of keyword?", named arguments to functions can only be strings, so trying to use non-string names guarantees the names can't match. CPython takes advantage of this by using special purpose lookup functions for `dict`s guaranteed to be composed exclusively of strings (which a lot of implementation internals happen to be), so the rejecting non-strings ensures strings can go through the fastest code paths (speeding up all Python code). – ShadowRanger Nov 08 '18 at 15:53

5 Answers5

8

When dictionaries are iterated as lists the iteration takes the keys of it, for example

for key in tmp:
    print(key)

is the same as

for key in tmp.keys():
    print(key)

in this case, unpacking as *tmp is equivalent to *tmp.keys(), ignoring the values. If you want to use the values you can use *tmp.values().

Double asterisk is used for when you define a function with keyword parameters such as

def foo(a, b):

or

def foo(**kwargs):

here you can store the parameters in a dictionary and pass it as **tmp. In the first case keys must be strings with the names of the parameter defined in the function firm. And in the second case you can work with kwargs as a dictionary inside the function.

vlizana
  • 2,962
  • 1
  • 16
  • 26
  • 2
    It would probably be helpful to link to the docs on [expressions](https://docs.python.org/3/reference/expressions.html#calls) where both of these are formally explained. ("If the syntax *expression appears in the function call, expression must evaluate to an iterable.") – Brad Solomon Nov 05 '18 at 04:07
  • 2
    Not a bad response but I want to poke to improve your answer (esp. since this topic is actually annoyingly complex). You can require keyword arguments without setting defaults, called [keyword-only args](https://www.python.org/dev/peps/pep-3102/). Also collecting keyword arguments with `**kwargs` is a little bit different than they would be with kw-only args or kwargs with default values---in particular, you can send in strings that are not valid python identifiers. – alkasm Nov 05 '18 at 04:09
  • 1
    "Double asterisk is used for when you define a function with keyword parameters (with a default value)" is somewhat not precise. If I do `def add(a,b): return a+b` and `tmp = {'a':1,'b':2}` and call 'add(**tmp)', I can still get the value of three. So the function parameters does not need default values? – Han XIAO Nov 05 '18 at 04:09
  • While a great answer, technically speaking you didn't address why they are getting the error they are getting. Specifically int is being used as a keyword. If their dict had string keys it would have given a different error. – Jab May 24 '23 at 18:30
3
def foo(a,b)
   return a+b

tmp = {1:2,3:4}
foo(*tmp)        #you get 4
foo(**tmp) 

In this case:
foo(*tmp) mean foo(1, 3)
foo(**tmp) mean foo(1=2, 3=4), which will raise an error since 1 can't be an argument. Arg must be strings and (thanks @ Alexander Reynolds for pointing this out) must start with underscore or alphabetical character. An argument must be a valid Python identifier. This mean you can't even do something like this:

def foo(1=2, 3=4):
   <your code>

or

def foo('1'=2, '3'=4):
   <your code>

See python_basic_syntax for more details.

enamoria
  • 896
  • 2
  • 11
  • 29
  • 1
    You can improve your answer by linking to documentation; as it stands right now it is not quite correct (e.g., '2' is a string but is not a valid python identifier). – alkasm Nov 05 '18 at 04:05
1

It is a Extended Iterable Unpacking.

>>> def add(a=0, b=0):
...     return a + b
...
>>> d = {'a': 2, 'b': 3}
>>> add(**d)#corresponding to add(a=2,b=3)
5

For single *,

def add(a=0, b=0):
    ...     return a + b
    ...
    >>> d = {'a': 2, 'b': 3}
    >>> add(*d)#corresponding to add(a='a',b='b')
    ab

Learn more here.

0

I think the ** double asterisk in function parameter and unpacking dictionary means intuitively in this way:

#suppose you have this function
def foo(a,**b):
    print(a)
    for x in b:
        print(x,"...",b[x])
#suppose you call this function in the following form
foo(whatever,m=1,n=2)   
#the m=1 syntax actually means assign parameter by name, like foo(a = whatever, m = 1, n = 2)
#so you can also do foo(whatever,**{"m":1,"n":2})
#the reason for this syntax is you actually do
**b is m=1,n=2 #something like pattern matching mechanism
so b is {"m":1,"n":2}, note "m" and "n" are now in string form
#the function is actually this:
def foo(a,**b):  # b = {"m":1,"n":2}
    print(a)
    for x in b:  #for x in b.keys(), thanks to @vlizana answer
        print(x,"...",b[x])

All the syntax make sense now. And it is the same for single asterisk. It is only worth noting that if you use single asterisk to unpack dictionary, you are actually trying to unpack it in a list way, and only key of dictionary are unpacked.

Han XIAO
  • 1,148
  • 9
  • 20
0

[https://docs.python.org/3/reference/expressions.html#calls]

A consequence of this is that although the *expression syntax may appear after explicit keyword arguments, it is processed before the keyword arguments (and any **expression arguments – see below). So:

def f(a, b):
print(a, b)

f(b=1, *(2,))
f(a=1, *(2,))
#Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
#TypeError: f() got multiple values for keyword argument 'a'
f(1, *(2,))
Z. K
  • 7
  • 4
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 09 '22 at 12:36