What seems like a simple pattern for allowing a variable number of positional arguments along with a defaulted named arg doesn't work.
I think I can overcome this by using **kwargs
, but is there something more straightforward I am doing wrong?
Why doesn't this work?
class Foo(obect):
"""Baffling"""
def __init__(self, bar=None, *args):
pass
foo = Foo(1) #works
foo = Foo(1, bar=2) # explodes
----> 1 foo = Foo(1, bar='baz')
TypeError: __init__() got multiple values for keyword argument 'bar'
How is the above code passing a duplicate bar
keyword argument?
Named arguments can be passed both by position and by name:
>>> def foo(a):
... print(a)
...
>>> foo(1)
1
>>> foo(a=2)
2
If you pass positional and keyword arguments, they get assigned in order. In your case, 1 gets assigned to the first positional argument (bar
), then bar=2
assigns the the argument named bar
. Thus, both assign to the name bar
, creating a conflict.
You can pass additional arguments after your named one:
>>> def foo(bar=None, *args):
... print('args=%r, bar=%r' % (args, bar))
...
... foo(2, 1) # bar=2, *args=(1,)
args=(1,), bar=2
In Python3, you can also make your parameter keyword only:
>>> def foo(*args, bar=None):
... print('args=%r, bar=%r' % (args, bar))
...
... foo(1, bar=2)
args=(1,), bar=2
This also works when you do not take any variadic arguments:
>>> def foo(*, bar=None):
... print('args=%r, bar=%r' % ('undefined', bar))
...
... foo(bar=2)
args='undefined', bar=2
>>> foo(1, bar=2)
TypeError: foo() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
In Python2, only **kwargs
is allowed after *args
. You can emulate named parameters by popping them from kwargs
:
>>> def foo(*args, **kwargs):
... bar = kwargs.pop("bar")
... print('args=%r, bar=%r' % (args, bar))
...
... foo(1, bar=2)
args=(1,), bar=2
If you want named-only parameters without variadic positional and named parameters, you must raise errors yourself:
>>> def foo(*args, **kwargs):
... bar = kwargs.pop('bar')
... if args or kwargs:
... raise TypeError
... print('args=%r, bar=%r' % ('undefined', bar))
...
... foo(bar=2)
args='undefined', bar=2
>>> foo(1, bar=2)
TypeError