Reputation: 8631
How can one define the initial contents of the keyword arguments dictionary? Here's an ugly solution:
def f(**kwargs):
ps = {'c': 2}
ps.update(kwargs)
print(str(ps))
Expected behaviour:
f(a=1, b=2) => {'c': 2, 'a': 1, 'b': 2}
f(a=1, b=2, c=3) => {'c': 3, 'a': 1, 'b': 2}
Yet, I would like to do something a little bit more in the lines of:
def f(**kwargs = {'c': 2}):
print(str(kwargs))
Or even:
def f(c=2, **kwargs):
print(str(???))
Ideas?
Upvotes: 2
Views: 476
Reputation: 393
First to address the issues with your current solution:
def f(**kwargs):
ps = {'c': 2}
ps.update(kwargs)
print(str(ps))
This creates a new dictionary and then has to take the time to update it with all the values from kwargs. If kwargs is large that can be fairly inefficient and as you pointed out is a bit ugly.
Obviously your second isn't valid.
As for the third option, an implementation of that was already given by Austin Hastings
If you are using kwargs and not keyword arguments for default values there's probably a reason (or at least there should be; for example an interface that defines c
without explicitly requiring a
and b
might not be desired even though the implementation may require a value for c
).
A simple implementation would take advantage of dict.setdefault
to update the values of kwargs if and only if the key is not already present:
def f(**kwargs):
kwargs.setdefault('c', 2)
print(str(kwargs))
Now as mentioned by the OP in a previous comment, the list of default values may be quite long, in that case you can have a loop set the default values:
def f(**kwargs):
defaults = {
'c': 2,
...
}
for k, v in defaults.items():
kwargs.setdefault(k, v)
print(str(kwargs))
A couple of performance issues here as well. First the defaults
dict literal gets created on every call of the function. This can be improved upon by moving the defaults outside of the function like so:
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
for k, v in DEFAULTS.items():
kwargs.setdefault(k, v)
print(str(kwargs))
Secondly in Python 2, dict.items
returns a copy of the (key, value) pairs so instead dict.iteritems
or dict.viewitems
allows you to iterate over the contents and thus is more efficient. In Python 3, 'dict.items` is a view so there's no issue there.
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
for k, v in DEFAULTS.iteritems(): # Python 2 optimization
kwargs.setdefault(k, v)
print(str(kwargs))
If efficiency and compatibility are both concerns, you can use the six
library for compatibility as follows:
from six import iteritems
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
for k, v in iteritems(DEFAULTS):
kwargs.setdefault(k, v)
print(str(kwargs))
Additionally, on every iteration of the for
loop, a lookup of the setdefault
method of kwargs
needs to be performed. If you truly have a really large number of default values a micro-optimization is to assign the method to a variable to avoid repeated lookup:
from six import iteritems
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
setdefault = kwargs.setdefault
for k, v in iteritems(DEFAULTS):
setdefault(k, v)
print(str(kwargs))
Lastly if the number of default values is instead expected to be larger than the number of kwargs, it would likely be more efficient to update the default with the kwargs. To do this, you can't use the global default or it would update the defaults with every run of the function, so the defaults need to be moved back into the function. This would leave us with the following:
def f(**kwargs):
defaults = {
'c': 2,
...
}
defaults.update(kwargs)
print(str(defaults))
Enjoy :D
Upvotes: 3
Reputation: 15310
Have you tried:
def f(c=2, **kwargs):
kwargs['c'] = c
print(kwargs)
Update
Barring that, you can use inspect
to access the code object, and get the keyword-only args from that, or even all the args:
import inspect
def f(a,b,c=2,*,d=1,**kwargs):
code_obj = inspect.currentframe().f_code
nposargs = code_obj.co_argcount
nkwargs = code_obj.co_kwonlyargcount
localvars = locals()
kwargs.update({k:localvars[k] for k in code_obj.co_varnames[:nposargs+nkwargs]})
print(kwargs)
g=f
g('a', 'b')
Upvotes: 0
Reputation: 155438
A variation possible on your first approach on Python 3.5+ is to define the default and expand the provided arguments on a single line, which also lets you replace kwargs
on the same line, e.g.:
def f(**kwargs):
# Start with defaults, then expand kwargs which will overwrite defaults if
# it has them
kwargs = {'c': 2, **kwargs}
print(str(kwargs))
Another approach (that won't produce an identical string) that creates a mapping that behaves the same way using collections.ChainMap
(3.3+):
from collections import ChainMap
def f(**kwargs):
# Chain overrides over defaults
# {'c': 2} could be defined outside f to avoid recreating it each call
ps = ChainMap(kwargs, {'c': 2})
print(str(ps))
Like I said, that won't produce the same string output, but unlike the other solutions, it won't become more and more costly as the number of passed keyword arguments increases (it doesn't have to copy them at all).
Upvotes: 1