Specifying Keyword Arguments with * args and ** kws
I found behavior in Python that puzzled and annoyed me, and I was wondering if I was wrong.
I have a function that needs to take an arbitrary number of arguments and keywords, but also needs to have some default keywords that make up its actual interface:
def foo(my_keyword0 = None, my_keyword1 = 'default', *args, **kws):
for argument in args:
print argument
The problem is, if I try to call foo(1, 2, 3)
, I only get a printout for 3, and the values 1 and 2 override the keyword arguments.
On the other hand, if I try to move my keywords after *args
or after **kws
, it will throw a syntax error. The only solution I found in this problem is to extract the keyword arguments from **kws
and set them to default values:
def foo(*args, **kws):
my_keyword0 = None if 'my_keyword0' not in kws else kws.pop('my_keyword0')
my_keyword0 = 'default' if 'my_keyword1' not in kws else kws.pop('my_keyword1')
for argument in args:
print argument
This is awful because it makes me add meaningless code and because the function signature becomes harder to understand - you should actually read the function code, not just look at its interface.
What am I missing? Isn't there a better way to do this?
source to share
Function arguments with default values are still positional arguments, and therefore the result you see is correct. When you supply a default value for a parameter, you are not creating a keyword argument. The default values are simply used when parameters are not supplied by the function call.
>>> def some_function(argument="Default"):
... # argument can be set either using positional parameters or keywords
... print argument
...
>>> some_function() # argument not provided -> uses default value
Default
>>> some_function(5) # argument provided, uses it
5
>>> some_function(argument=5) # equivalent to the above one
5
>>> def some_function(argument="Default", *args):
... print (argument, args)
...
>>> some_function() #argument not provided, so uses the default and *args is empty
('Default', ())
>>> some_function(5) # argument provided, and thus uses it. *args are empty
(5, ())
>>> some_function(5, 1, 2, 3) # argument provided, and thus uses it. *args not empty
(5, (1, 2, 3))
>>> some_function(1, 2, 3, argument=5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: some_function() got multiple values for keyword argument 'argument'
Pay attention to the last error message: as you can see what is 1
assigned argument
and then python re-discovered keyword
by referencing argument
, and thus raised the error.
*args
are assigned only after all possible positional arguments have been assigned.
In python2, it is not possible to define a meaning only for a keyword other than using**kwargs
. As a workaround, you can do something like:
def my_function(a,b,c,d,*args, **kwargs):
default_dict = {
'my_keyword1': TheDefaultValue,
'my_keyword2': TheDefaultValue2,
}
default_dict.update(kwargs) #overwrite defaults if user provided them
if not (set(default_dict) <= set('all', 'the', 'possible', 'keywords')):
# if you want to do error checking on kwargs, even though at that
# point why use kwargs at all?
raise TypeError('Invalid keywords')
keyword1 = default_dict['keyword1']
# etc.
In python3, you can define keyword-only arguments:
def my_function(a,b,c,*args, keyword, only=True): pass
# ...
Note that the keyword does not mean that it should have a default value.
source to share
In Python, non-keyword arguments cannot appear after a keyword argument, so the signature of the function you are trying to use is not possible:
foo(my_keyword0="baa", my_keyword1="boo", 1, 2, 3, bar="baz", spam="eggs") # won't work
If you think about it, there are very good reasons for this limitation (hint: keyword arguments can be presented in any order, and positional arguments ... well, positional ones)
From the documentation :
In a function call, keyword arguments must follow positional arguments. All keyword arguments passed must match one of the arguments accepted by the function (for example, actor is not a valid argument for the parrot function), and their order is not important. This also includes optional arguments
*args
and **kwargs
contain arguments that do not match the existing formal parameter, so once you have specified spam=None
in the argument list, it will no longer be passed **kwargs
:
When a final formal parameter of the form name is present , it receives a dictionary (see Collation Types - dict) containing all the ** keyword arguments except those matching the formal parameter. This can be combined with a formal parameter of the form * name (described in the next subsection), which receives a tuple containing positional arguments outside of the official parameter list. (* name must be specified before ** name.)
The closest approximation to your (impossible) syntax would be something like
def foo(my_keyword0=None, my_keyword1='default', numbers=None, **kwargs)
which you would use like
foo(my_keyword0="bar", my_keyword1="baz", numbers=(1,2,3), spam='eggs')
foo("bar", "baz", numbers=(1,2,3))
foo("bar", "baz", (1,2,3))
foo("bar", "baz", (1,2,3), spam="eggs")
foo(numbers=(1,2,3))
foo(spam="eggs")
etc.
which could possibly be more readable and less unexpected than your original idea.
source to share