Python dictionary and functions

Asked

Viewed 238 times

5

I don’t understand why the following code says that it is receiving more than one pro value same parameter.

def foo(name, **kwds):
    return 'name' in kwds
foo(1, **{'name' : 2})´

Typeerror: foo() got Multiple values for argument 'name'

But when I use this other way it works:

def foo(name, /, **kwds):
    return 'name' in kwds
foo(1, **{'name' : 2})

2 answers

8


Let’s go in pieces. First, let’s see what the / ago.

If you declare a function like this:

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

It is possible to call it several different ways:

# passando os parâmetros posicionais
f(1, 2) # 1 2

# usando os nomes dos parâmetros
f(a=1, b=2) # 1 2

# usando os nomes, podemos inclusive passá-los em qualquer ordem
f(b=2, a=1) # 1 2

But if we use the / in the declaration of parameters (resource that was added in Python 3.8), that means all arguments before the / cannot be passed using the names. See the difference for the previous example:

def f(a, b, /): # "a" e "b" não podem ser passados com nomes
    print(a, b)

# passando os parâmetros posicionais, funciona
f(1, 2) # 1 2

# usando os nomes, dá erro
f(a=1, b=2) # TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'

And any parameter that is after the / can be passed with or without the name:

def f(a, b, /, c, d):
    print(a, b, c, d)

# "c" e "d" podem ser passados posicionalmente
f(1, 2, 3, 4) # 1 2 3 4

# "c" e "d" podem ser passados com os nomes
f(1, 2, c=3, d=4) # 1 2 3 4
f(1, 2, d=4, c=3) # 1 2 3 4

# "a" e "b" não podem ser passados com nomes
f(a=1, b=2, c=3, d=4) # TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'

Now, if we use keyword Arguments:

def foo(name, **kwds):
    print(name, kwds)

By calling foo(1, **{'name' : 2}), in the end is the same as doing foo(1, name=2) (see this example in the documentation). But as the first parameter is called name, then it’s like I’m doing foo(name=1, name=2). Hence the error message: "foo() got Multiple values for argument 'name'", because he understands that I am passing two different values to name.

Already using the /:

def foo(name, /, **kwds):
    print(name, kwds)

foo(1, **{'name' : 2}) # 1 {'name': 2}
foo(1, name=2) # 1 {'name': 2}

Now it works because, like the / indicates that the parameter name is just positional, his name (name) can be used in keyword Arguments, without the possibility of "collision" of names.

Even this example is cited in documentation:

Since the Parameters to the left of / are not Exposed as possible Keywords, the Parameters Names remain available for use in **kwargs:

>>> def f(a, b, /, **kwargs):
...     print(a, b, kwargs)
...
>>> f(10, 20, a=1, b=2, c=3)         # a and b are used in two ways
10 20 {'a': 1, 'b': 2, 'c': 3}

In free translation: "Like the parameters on the left of / are not exposed as Keywords, their names are available for use in **kwargs".

The first a receives the amount 10, but as it is only a positional parameter (thanks to the /), there is no ambiguity in the call: I know that the a=1 refers to the kwargs, since the first a cannot be called by name.

  • 1

    So what happens is that **{'name' : 2} is the same thing as name = 2, when used as a parameter? I guess I understood then worth

  • @Henriquehott Yes, inclusive in the documentation has an example that shows this

4

It always causes confusion.

The * and ** are for "unpack" the received variable in the function. For me, the unpacking word can cause confusion.

Usually used as *args and **kwargs, what they do is:

*args - pegar os argumentos posicionais e transformar em uma tupla
**kwargs - pegar os argumentos nomeados e transformar em um dicionário

Take the example:

>>> def func(one, *args, **kwargs):
...     print(one)
...     print(args)
...     print(kwargs)
...
>>> func(1, "a", "b", 5, firstName="Joao", lastName="Silva")
1
('a', 'b', 5)
{'firstName': 'Joao', 'lastName': 'Silva'}

Note that the number 1 was assigned to the variable one. The remaining positional parameters became one tuple called args. Already the parameters named firstName and lastName turned into the dictionary kwargs

For more details visit this website.

As for /, it indicates that some function parameters must be specified positionally and cannot be used as keyword arguments.

In other words, or better showing example:

>>> def f(a, b, /, c, d, *, e, f):
...     print(c)
...
>>> f(10, 20, 30, d=40, e=50, f=60)
30

>>> f(a=10, b=20, c=30, d=40, e=50, f=60)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'

>>> f(10, 20, 30, 40, 50, 60)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 4 positional arguments but 6 were given
>>> f(10, 20, 30, 40, e=50, f=60)
30

Modified example of documentation

That is, all the parameters before the / nay may be appointed.

The parameters after the * have than be appointed

In short:

def name(somente_parametros_posicionais, /, parametros_posicionais_ou_nomeados, *, somente_parametros_nomeados):

I hope I’ve helped.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.