I tried to use * and ** to pass any number of arguments to a function. In "Learning Python" authored by Mark Lutz, it says to follow the order of positional (value) first, then a combination of keyword arguments (name=value) and *sequence, followed by **dict. However, I found that the positional arguments need to come first if present, but the rest of three, to certain extent, can be mixed in order.
Code keywords3.py:
def func(a, b=1, *pArgs, **kwArgs):
print("a = {0}, b = {1}".format(a,b))
print("Positional args = {}".format(pArgs))
print("Keyword args = {}".format(kwArgs))
By trial-and-error,
[1] Between keywords and **dict, they can be in any order...
>>> import keywords3 as j
>>> j.func(b = 3, **{'a':2,'c':4,'d':5})
a = 2, b = 3
Positional args = ()
Keyword args = {'d': 5, 'c': 4}
>>> j.func( **{'a':2}, b = 3, **{'c':4})
a = 2, b = 3
Positional args = ()
Keyword args = {'c': 4}
[2] Between positional args and *sequence, they can be in any order...
>>> j.func(*(2, 3), 4, *(5, 6))
a = 2, b = 3
Positional args = (4, 5, 6)
Keyword args = {}
>>> j.func(2, *(3, 4), 5, *(6,7), **{'c':8})
a = 2, b = 3
Positional args = (4, 5, 6, 7)
Keyword args = {'c': 8}
[3] In general, positional or *sequence arguments need to appear before keyword or **dict arguments.
>>> j.func(*(3, 4), 5, *(6,7), d=15, **{'c':8}, e=16)
a = 3, b = 4
Positional args = (5, 6, 7)
Keyword args = {'e': 16, 'd': 15, 'c': 8}
>>> j.func(d=15, 5, *(6,7), **{'c':8}, e=16)
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> j.func(**{'a':2}, 5, *(6,7), **{'c':8}, e=16)
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument unpacking
>>> j.func(**{'a':2}, *(6,7), **{'c':8}, e=16)
File "<stdin>", line 1
SyntaxError: iterable argument unpacking follows keyword argument unpacking
[4] One exception is that iterable argument unpacking *(6,7) following keyword argument is ok...
>>> j.func(f=5, *(6,7), **{'c':8}, e=16)
a = 6, b = 7
Positional args = ()
Keyword args = {'e': 16, 'f': 5, 'c': 8}
Are these observations correct? Please comment.
There is one single rule consistent with all your examples: positional arguments go before named arguments.
In all your examples, * and ** are unpacking operators. So, for example, when you write
f(1, *(2,3), 4)
the language gets
f(1,2,3,4)
They are all positional arguments, the language doesn't know the difference. Similarly for the ** operator.
However, when you violate that only rule, eg
j.func(**{'a':2}, 5, *(6,7), **{'c':8}, e=16)
you get an error, becuase, in this example, **{'a':2} is equivalent to a=2, which precedes the positional argument 5.
Related
This question already has answers here:
Calling a Python function with *args,**kwargs and optional / default arguments
(4 answers)
Closed 4 years ago.
I'm trying to wrap my head around using args and kwargs in Python 3 (Python 3.7.0) but I'm running into some issues with my understanding.
Here is a simple function I have:
def add_args(y=10, *args, **kwargs):
return y, args, kwargs
And test it to see what is returned:
print(add_args(1, 5, 10, 20, 50))
print(add_args())
>>
(1, (5, 10, 20, 50), {}) # What happened to my default y=10?
(10, (), {})
What I don't understand is, what happened to y=10 in the first print statement? I can see it is being overridden by the 1 in args but I'm unsure why.
How can I rewrite this function so the default value is not overridden, or am I missing something with how the parameters are passed from the function signature to the return statement?
I tried looking here and here but did not find the answers I was looking for. As I thought putting the default values before the args and kwargs would prevent the overwriting.
*args only captures any positional arguments not otherwise defined; y=10 does not mean y can't be used as a positional argument. So y is assigned the first positional argument.
You can prevent y being used as a positional argument by making it a keyword-only argument. You do this by placing the argument after the *args var-positional catch-all parameter, or if you don't have a *name parameter, after a * single asterisk:
def add_args(*args, y=10, **kwargs):
return y, args, kwargs
or
def keyword_only_args(*, y=10, **kwargs):
return y, kwargs
Now y won't capture positional arguments any more:
>>> def add_args(*args, y=10, **kwargs):
... return y, args, kwargs
...
>>> add_args(1, 5, 10, 20, 50)
(10, (1, 5, 10, 20, 50), {}) # y is still 10
>>> add_args(1, 5, 10, 20, 50, y=42) # setting y explicitly
(42, (1, 5, 10, 20, 50), {})
You don't have to have a **kwargs keyword catch-all either:
def add_args(*args, y=10):
return y, args
but if it is present, it needs to be listed last.
Keyword-only arguments do not have to have a default value, the =10 can be omitted, but then the parameter becomes mandatory, and can only be specified in a call by using y=value:
>>> def add_args(*args, y): # mandatory keyword-only argument
... return y, args
...
>>> add_args(1, 5, 10, 20, 50)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: add_args() missing 1 required keyword-only argument: 'y'
>>> add_args(1, 5, 10, 20, 50, y=42)
(42, (1, 5, 10, 20, 50))
Hmm, y is the first positional argument in your function definition, so naturally it binds to the first actual positional argument at callsite, which is 1.
This question already has answers here:
What do *args and **kwargs mean? [duplicate]
(5 answers)
Closed 7 years ago.
I suppose I understand how to use them individually in functions like ...
f(*args) or f(**kargs)
But what if a function accepts both as arguments?
Ex: f(*args, **kargs)
Yes, it works
def f(*args, **kwargs):
pass
If you call function like this f(1, 3, "foo", [1, 2, 10], a=1, apple=33), then in function args will be (1, 3, "foo", [1, 2, 10]), kwargs will be{'a': 1, 'apple': 33}.
It will work too
def f(a, b, foo, *args, **kwargs):
pass
But if we call this function with the same arguments, a will be 1, b will be 3, foo will be "foo", args will be ([1, 2, 10]), kwargs will be the same.
*args gets positional arguments; **kwargs gets named arguments. For instance:
f(1, 2, 3, a=1, b=2, c=3)
would pass
args = (1, 2, 3)
kwargs = {a: 1, b: 2, c: 3}
This question already has answers here:
Python, default keyword arguments after variable length positional arguments
(2 answers)
Closed 9 years ago.
I am trying to figure out if I can leave an optional argument out (use it's default value) when using *args in Python. The following code works through "print(a)", so explicitly including the optional argument h in the deriv function call works. Can I leave it (h) out somehow? My attempts ("b = ...", "c = ...", "d = ...") fail. Is there another way?
def deriv(f, x, h=1.e-9, *params):
return (f(x+h, *params)-f(x-h, *params))/(2.*h)
def f1(x, a, p):
return a*x**p
a = deriv(f1, 3, 1.e-9, 4, 5)
print(a)
b = deriv(f1, 3, , 4, 5)
c = deriv(f1, 3, 4, 5)
d = deriv(f1, 3, h, 4, 5)
No, python applies positional arguments to all named arguments first; h is the third positional argument in the function signature and thus only argument positions 4 and over are captured by *params.
Instead, use a **kwargs argument catching arbitrary keyword arguments and look h up in that:
def deriv(f, x, *params, **kwargs):
h = kwargs.pop('h', 1.e-9)
You'll now have to name h explicitly when calling deriv:
b = deriv(f1, 3, 4, 5, h=2.0)
It looks like you're using Python 3, so you can use keyword-only arguments:
>>> def deriv(f, x, *params, h=1.0E-9):
print(f)
print(x)
print(params)
print(h)
>>> deriv(pow, 'x', 10, 20, 30)
<built-in function pow>
x
(10, 20, 30)
1e-09
>>> deriv(pow, 'x', 10, 20, 30, h=.2)
<built-in function pow>
x
(10, 20, 30)
0.2
In Python, I wrote this:
bvar=mht.get_value()
temp=self.treemodel.insert(iter,0,(mht,False,*bvar))
I'm trying to expand bvar to the function call as arguments.
But then it returns:
File "./unobsoluttreemodel.py", line 65
temp=self.treemodel.insert(iter,0,(mht,False,*bvar))
^
SyntaxError: invalid syntax
What just happen? It should be correct right?
Update: this behavior was fixed in Python 3.5.0, see PEP-0448:
Unpacking is proposed to be allowed inside tuple, list, set, and dictionary displays:
*range(4), 4
# (0, 1, 2, 3, 4)
[*range(4), 4]
# [0, 1, 2, 3, 4]
{*range(4), 4}
# {0, 1, 2, 3, 4}
{'x': 1, **{'y': 2}}
# {'x': 1, 'y': 2}
If you want to pass the last argument as a tuple of (mnt, False, bvar[0], bvar[1], ...) you could use
temp = self.treemodel.insert(iter, 0, (mht,False)+tuple(bvar) )
The extended call syntax *b can only be used in calling functions, function arguments, and tuple unpacking on Python 3.x.
>>> def f(a, b, *c): print(a, b, c)
...
>>> x, *y = range(6)
>>> f(*y)
1 2 (3, 4, 5)
Tuple literal isn't in one of these cases, so it causes a syntax error.
>>> (1, *y)
File "<stdin>", line 1
SyntaxError: can use starred expression only as assignment target
Not it isn't right. Parameters expansion works only in function arguments, not inside tuples.
>>> def foo(a, b, c):
... print a, b, c
...
>>> data = (1, 2, 3)
>>> foo(*data)
1 2 3
>>> foo((*data,))
File "<stdin>", line 1
foo((*data,))
^
SyntaxError: invalid syntax
You appear to have an extra level of parentheses in there. Try:
temp=self.treemodel.insert(iter,0,mht,False,*bvar)
Your extra parentheses are trying to create a tuple using the * syntax, which is a syntax error.
For python, I could use unpacking arguments as follows.
def hello(x, *y, **z):
print 'x', x
print 'y', y
print 'z', z
hello(1, *[1,2,3], a=1,b=2,c=3)
hello(1, *(1,2,3), **{'a':1,'b':2,'c':3})
x = 1
y = (1, 2, 3)
z = {'a': 1, 'c': 3, 'b': 2}
But, I got an error if I use keyword argument as follows.
hello(x=1, *(1,2,3), **{'a':1,'b':2,'c':3})
TypeError: hello() got multiple values for keyword argument 'x'
Why is this?
Regardless of the order in which they are specified, positional arguments get assigned prior to keyword arguments. In your case, the positional arguments are (1, 2, 3) and the keyword arguments are x=1, a=1, b=2, c=3. Because positional arguments get assigned first, the parameter x receives 1 and is not eligible for keyword arguments any more. This sounds a bit weird because syntactically your positional arguments are specified after the keyword argument, but nonetheless the order “positional arguments → keyword arguments” is always adhered to.
Here is a simpler example:
>>> def f(x): pass
...
>>> f(1, x=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for keyword argument 'x'
>>> f(x=2, *(1,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for keyword argument 'x'