Reputation: 69
I am trying to define a function like so:
def get_event_stats(elengths, einds, *args, **kwargs):
master_list = []
if avg:
for arg in args:
do stuff...
if tot:
for arg in args:
do stuff...
return master_list
I would like elengths and einds to be fixed positional args (these are just arrays of ints). I am trying to use the function by passing it a variable length list of arrays as *args and some **kwargs, in this example two (avg and tot), but potentially more, for example,
avg_event_time = get_event_stats(event_lengths, eventInds, *alist, avg=True, tot=False)
where
alist = [time, counts]
and my kwargs are avg and tot, which are given the value of True and False respectively. Regardless of how I've tried to implement this function, I get some kind of error. What am I missing here in the correct usage of *args and **kwargs?
Upvotes: 1
Views: 3981
Reputation: 155497
**kwargs
creates a dict
, it doesn't inject arbitrary names into your local namespace. If you want to look for whether a particular keyword was passed, you can't test if avg:
(there is no variable named avg
), you need to check if avg
is in the dict
, e.g. if 'avg' in kwargs:
, or to check both existence and "truthiness", so passing avg=False
is equivalent to not passing it at all, test if kwargs.get('avg'):
(using kwargs.get('avg')
ensures no exception is thrown if avg
wasn't passed at all, unlike if kwargs['avg']:
).
Note: You should really move to Python 3 if at all possible. It makes writing this function much more obvious and clean, as you could avoid the need for kwargs
completely, and verify no unrecognized keyword arguments were passed by just defining the function as:
def get_event_stats(elengths, einds, *args, avg=False, tot=False):
master_list = []
if avg:
for arg in args:
do stuff...
if tot:
for arg in args:
do stuff...
return master_list
Note how the body of the function you already wrote works without modification if you explicitly name your keyword arguments after the positional varargs, making your code far more self-documenting (as well as more efficient, and with better self-checks; the clean Py3 code will error out informing you of the unrecognized argument if you pass avrg=True
to it, while the **kwargs
approach would require explicit checks for unknown arguments that would slow you down and bloat the code.
The closest you could get to the Py3 error-checks with minimal overhead and similar correctness/readability would be:
def get_event_stats(elengths, einds, *args, **kwargs):
master_list = []
# Unpack the recognized arguments (with default values), so kwargs left should be empty
avg = kwargs.pop('avg', False)
tot = kwargs.pop('tot', False)
# If any keywords left over, they're unrecognized, raise an error
if kwargs:
# Arbitrarily select alphabetically first unknown keyword arg
raise TypeError('get_event_stats() got an unexpected keyword argument {!r}'.format(min(kwargs)))
if avg:
for arg in args:
do stuff...
if tot:
for arg in args:
do stuff...
return master_list
Upvotes: 3
Reputation: 15987
If you meant that avg
and tot
should be passed in as keyword args, like in your example get_event_stats(..., avg=True, tot=False)
then they are populated in kwargs
. You can look them up in the kwargs
dict using a key lookup (like kwargs['avg']
.
However if they are not present at all, then that will give a key error, so use it with the dict.get()
method: kwargs.get('avg')
which returns None
if it is not present, which is boolean False
. Or use kwargs.get('avg', False)
if you explicitly want a False
if it's not present.
def get_event_stats(elengths, einds, *args, **kwargs):
master_list = []
if kwargs.get('avg'):
for arg in args:
do stuff...
if kwargs.get('tot'):
for arg in args:
do stuff...
return master_list
Upvotes: 2