I'm trying to write a function that will contain some default values, with the possibility of having some additional parameters sent in. For example, I would like something along the lines of this:
def defaultsAndArbitrary(arr, example1=True, example2=13, *modifiers):
#code
Then, I'd like to be able to call the function like so:
defaultsAndArbitrary([1,5,3,9], modifier1, modifier2)
With that call, the values for the arguments should be:
arr = [1,5,3,9]
example1 = True
example2 = 13
modifiers = (modifier1, modifier2)
There's a similar question here:
Having arbitrary number of arguments with a named default in python
But it doesn't really answer this question (at least, to my understanding). Is it even possible to do this?
Thanks.
You can do something like this:
def defaultsAndArbitrary(arr, *modifiers, **kwargs):
example1 = kwargs.get('example1',True)
example2 = kwargs.get('example2',13)
In Python 3 you can use keyword-only arguments. That would look something like this:
def defaultsAndArbitrary(arr, *modifiers, example1=True, example2=13):
In Python 2 you must a use a solution like that given in the question you linked to: you must use **kwargs and manually extract example1 and example2 from the kwargs dict, supplying defaults if those kwargs are not passed. This means that you can't specify their names or defaults in the function signature; you have to do it in the code of the function.
Note that in either case, your example1 and example2 variables must be passed by keyword (if you want to pass values for them instead of using the defaults); they cannot be passed positionally.
Related
For this function
def eat_dog(name, should_digest=True):
print "ate dog named %s. Digested, too? %" % (name, str(should_digest))
I want to, external to the function, read its arguments and any default values attached. So for this specific example, I want to know that name has no default value (i.e. that it is a required argument) and that True is the default value for should_digest.
I'm aware of inspect.getargspec(), which does give me information about arguments and default values, but I see no connection between the two:
ArgSpec(args=['name', 'should_digest'], varargs=None, keywords=None, defaults=(True,))
From this output how can I tell that True (in the defaults tuple) is the default value for should_digest?
Additionally, I'm aware of the "ask for forgiveness" model of approaching a problem, but unfortunately output from that error won't tell me the name of the missing argument:
>>> eat_dog()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: eat_dog() takes at least 1 argument (0 given)
To give context (why I want to do this), I'm exposing functions in a module over a JSON API. If the caller omits certain function arguments, I want to return a specific error that names the specific function argument that was omitted. If a client omits an argument, but there's a default provided in the function signature, I want to use that default.
Python3.x
In a python3.x world, you should probably use a Signature object:
import inspect
def get_default_args(func):
signature = inspect.signature(func)
return {
k: v.default
for k, v in signature.parameters.items()
if v.default is not inspect.Parameter.empty
}
Python2.x (old answer)
The args/defaults can be combined as:
import inspect
a = inspect.getargspec(eat_dog)
zip(a.args[-len(a.defaults):],a.defaults)
Here a.args[-len(a.defaults):] are the arguments with defaults values and obviously a.defaults are the corresponding default values.
You could even pass the output of zip to the dict constructor and create a mapping suitable for keyword unpacking.
looking at the docs, this solution will only work on python2.6 or newer since I assume that inspect.getargspec returns a named tuple. Earlier versions returned a regular tuple, but it would be very easy to modify accordingly. Here's a version which works with older (and newer) versions:
import inspect
def get_default_args(func):
"""
returns a dictionary of arg_name:default_values for the input function
"""
args, varargs, keywords, defaults = inspect.getargspec(func)
return dict(zip(args[-len(defaults):], defaults))
Come to think of it:
return dict(zip(reversed(args), reversed(defaults)))
would also work and may be more intuitive to some people.
Depending on exactly what you need, you might not need the inspect module since you can check the __defaults__ attribute of the function:
>>> eat_dog.__defaults__
(True,)
>>> eat_dog.__code__.co_argcount
2
>>> eat_dog.__code__.co_varnames
('name', 'should_digest')
>>>
>>> eat_dog.__kwdefaults__
>>> eat_dog.__code__.co_kwonlyargcount
0
You can use inspect module with its getargspec function:
inspect.getargspec(func)
Get the names and default values of a Python function’s arguments. A tuple of four things is returned: (args, varargs, keywords, defaults). args is a list of the argument names (it may contain nested lists). varargs and keywords are the names of the * and ** arguments or None. defaults is a tuple of default argument values or None if there are no default arguments; if this tuple has n elements, they correspond to the last n elements listed in args.
See mgilson's answer for exact code on how to retrieve argument names and their default values.
To those looking for a version to grab a specific default parameter with mgilson's answer.
value = signature(my_func).parameters['param_name'].default
Here's a full working version, done in Python 3.8.2
from inspect import signature
def my_func(a, b, c, param_name='apple'):
pass
value = signature(my_func).parameters['param_name'].default
print(value == 'apple') # True
to take care of keyword-only args (and because defaults and kwonlydefaults can be None):
spec = inspect.getfullargspec(func)
defaults = dict(zip(spec.args[::-1], (spec.defaults or ())[::-1]))
defaults.update(spec.kwonlydefaults or {})
You can get this via some of the __dunder__ vars as mentioned by other posts. Putting that into a simple helper function can get you a dictionary of default values.
.__code__.co_varnames: A tuple of all input variables
.__defaults__: A tuple of the default values
It is worth noting that this tuple only incudes the default provided variables which must always be positioned last in the function arguments
You can use these two items to match the last n variables in the .__code__.co_varnames with all the items in the .__defaults__
EDIT Thanks to #griloHBG - Added if statement to prevent exceptions when no defaults are specified.
def my_fn(a, b=2, c='a'):
pass
def get_defaults(fn):
if fn.__defaults__==None:
return {}
return dict(zip(
fn.__code__.co_varnames[-len(fn.__defaults__):],
fn.__defaults__
))
print(get_defaults(my_fn))
Should give:
{'b': 2, 'c': 'a'}
In python, all the arguments with default value come after the arguments without default value. So the mapping should start from the end till you exhaust the default value list. Hence the logic:
dict(zip(reversed(args), reversed(defaults)))
gives the correctly mapped defaults.
I'm new to programming and I've been stuck on this issue and would really like some help!
One of the parameters in my function is optional, but can take on multiple default values based on another function. Both functions take in the same input (among others). When I try to assign a default using the function as illustrated below:
def func(foo):
# returns different values of some variable k based on foo
def anotherFunc(foo, bar, k=func(foo)):
# this is the same foo input as the previous function
I get the following error:
NameError: name 'foo' is not defined
The thing is, the user can call 'anotherFunc' with any value of 'k' they want, which complicates things. Is there any way to have a function with arguments in it as a parameter in another function? Or is there any way for me to set multiple default values of 'k' based on the previous function while still allowing the user to choose their own 'k' if they wanted?
Thanks!
foo at the moment of defining the function acts as placeholder for the first function argument. It has no value until the function is called, for which its value can be accessed in the function body, like so:
def another_func(foo, bar, k=None):
if k is None:
k = func(foo)
...
You would probably want to do something like:
def func(foo):
return foo
def anotherfunc(foo, bar, k=None):
if k == None:
k = func(foo)
#process whatever
I want to convert certain values of a dictionary as multiple args and pass it to a method. The key of the dict needs to be the name of the variable passed to the method.
eg:
myDict={'one':1,'two':2,'three':3,'four':4}
#call myMethod as
myMethod(one=1,two=2)
def myMeth(self,*args):
do somthing with args
You can use the unpacking notation. If you have a method like this
def function(arg1,arg2,arg3):
# do something
and a dictionary dct = {'arg1':3,'arg2':3,'arg3':6}, you can call the function like
function(**dct)
which is equivalent to doing function(arg1=3,arg2=3,arg3=6).
Notice the double stars. This means to take the dictionary and pass its values as named parameters where the keys are the names. A single star would unpack a list, passing its values in order as unnamed parameters. These can be combined.
See section 4.7.4 of the python documentation for more detail. Here is another article discussing these as well.
Use ** for passing or accepting dictionary arguments. The following code
d={'foo': 'bar','test': 123}
def my_method(**kwargs):
print kwargs
my_method(**d)
would print the contents of d, {'foo': 'bar','test': 123}
EDIT: #Matthew 's answer sums it up nicely, via the comparison of *args.
for this,
import os.path
def f(data_file_path=os.path.join(os.getcwd(),'temp'),type):
...
return data
I get this,
SyntaxError: non-default argument follows default argument
Is there a way to make this work or do I have to define a variable such as,
rawdata_path = os.path.join(os.getcwd(),'temp')
and then plug that into the function?
Move type before data_file_path
def f(type,data_file_path=os.path.join(os.getcwd(),'temp')):
Assigning values in the function parameter called default arguments, those should come afther non-default arguments
You have to switch the order of the arguments. Mandatory arguments (without default values) must come before arguments with set default values.
Rearrange the parameters:
def f(type, data_file_path=os.path.join(os.getcwd(),'temp')):
pass
The reason for this is, that arguments with default values can be omitted.
But of you call f('foo'), it is not know if you want to set the type and omit data_file_path or not.
Arguments with a default value should be placed after all arguments without a default value.
Change it to:
import os.path
def f(type, data_file_path=os.path.join(os.getcwd(),'temp')):
...
return data
Never mind.
SyntaxError: non-default argument follows default argument
refers to the order of the arguments so,
def f(type,data_file_path=os.path.join(os.getcwd(),'temp')):
works!
me newbie
Does python have the ability to create dynamic keywords?
For example:
qset.filter(min_price__usd__range=(min_price, max_price))
I want to be able to change the usd part based on a selected currency.
Yes, It does. Use **kwargs in a function definition.
Example:
def f(**kwargs):
print kwargs.keys()
f(a=2, b="b") # -> ['a', 'b']
f(**{'d'+'e': 1}) # -> ['de']
But why do you need that?
If I understand what you're asking correctly,
qset.filter(**{
'min_price_' + selected_currency + '_range' :
(min_price, max_price)})
does what you need.
You can easily do this by declaring your function like this:
def filter(**kwargs):
your function will now be passed a dictionary called kwargs that contains the keywords and values passed to your function. Note that, syntactically, the word kwargs is meaningless; the ** is what causes the dynamic keyword behavior.
You can also do the reverse. If you are calling a function, and you have a dictionary that corresponds to the arguments, you can do
someFunction(**theDictionary)
There is also the lesser used *foo variant, which causes you to receive an array of arguments. This is similar to normal C vararg arrays.
Yes, sort of.
In your filter method you can declare a wildcard variable that collects all the unknown keyword arguments. Your method might look like this:
def filter(self, **kwargs):
for key,value in kwargs:
if key.startswith('min_price__') and key.endswith('__range'):
currency = key.replace('min_price__', '').replace('__range','')
rate = self.current_conversion_rates[currency]
self.setCurrencyRange(value[0]*rate, value[1]*rate)