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.
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
– Henrique Hott
@Henriquehott Yes, inclusive in the documentation has an example that shows this
– hkotsubo