Let's say I have a function f() which I know accepts 1 argument, action, followed by a variable number of arguments.
Depending on the initial action value, the function expects a different set of following arguments, e.g. for action 1 we know we must have 3 extra arguments p1, p2 and p3. Depending on how the function was called, these args can be either in args or `kwargs.
How do I retrieve them?
def f(action, *args, **kwargs):
if action==1:
#we know that the function should have 3 supplementary arguments: p1, p2, p3
#we want something like:
#p1 = kwargs['p1'] OR args[0] OR <some default> (order matters?)
#p1 = kwargs['p1'] OR args[0] OR <some default>
#p1 = kwargs['p1'] OR args[0] OR <some default>
Note that:
f(1, 3, 4, 5)
f(1,p1=3, p2=4, p3=5)
f(1, 2, p2=4, p3=5)
will place the different p1, p2, p3 parameters in either args or kwargs. I could try with nested try/except statements, like:
try:
p1=kwargs['p1']
except:
try:
p1=args[0]
except:
p1=default
but it does not feel right.
I would call a sub-function with a defined argument list within f:
def f(action, *args, **kwargs):
if action == 1:
f_1(*args, **kwargs)
...
def f_1(p1, p2, p3):
# now you know the args
Of course, this raises the question why you need the over-all function, but let's assume you have a reason for that ;). Else, you could also have a dictionary:
funcs = {1: f_1, 2: ....}
Then you can call the correct function with individual arguments as
funcs[action](p1, p2, p3)
I feel you were quite close to the answer on your question in out of itself.
You can do something like this:
def f(action, *args, **kwargs):
if action==1:
p1=kwargs.get('p1') or args[0] if len(args)>0 else 'default'
...
The order does matter. In this case if you the key arguments would take priority over positional ones.
Here are some input examples --> value of p1 in the function:
f(1,10)-->10
f(1,2,20)-->2
f(1,p1=10)-->10
f(1,10,p1=2)-->2
f(1,p3=20)-->'default'
You can try with, using OrderedDict:
from collections import OrderedDict
def f(action, *args, **kwargs):
params = OrderedDict({
'p1': 'default',
'p2': 'default',
'p3': 'default',
})
if action == 1:
for index, param in enumerate(params.keys()):
if index < len(args):
params[param] = kwargs[param] if param in kwargs else args[index]
else:
params[param] = kwargs[param] if param in kwargs else params[param]
# Do something with `params`.
f(1, 3, 4, 5)
f(1, p1=3, p2=4, p3=5)
f(1, 2, p2=4, p3=5)
If you need more parameters, just add them to the ordered dictionary.
Related
When I have a function with bunch of optional arguments
def function_name(arg1=0, arg2=0, arg3=0, arg4=0, ...):
.....doing anything
and I want to pass same value to some of them
function_name(arg1=1, arg3=1, arg6=1)
Is there a more compact way to do it?
so I don't have to keep copying =value and filling up the line.
After several helpful replies I think I came to the answer I was looking for:
def myargs(value, *args):
kargs = {}
for arg in args:
kargs[arg] = value
return kargs
function_name(**myargs(1, 'arg1', 'arg3', 'arg6'))
Thanks guys, sorry if I was bad at explaining what I needed.
I think what you are wanting to do is keyword argument unpacking. You can pass a dictionary to the function and it will unpack the arguments you have included. Anything not included uses the default values.
def test(w=0, x=0, y=0, z=0):
return w+x+y+z
kwargs_1 = {'y': 1}
kwargs_2 = {'x': 2, 'z': 3}
test(**kwargs_1)
# returns:
1
test(**kwargs_2)
# returns:
5
You can use default arguments:
def func(arg1, arg2, def_arg1="1", def_arg2="2"):
pass
You can just use **kwargs
def function_name(arg, argOpt1=0, argOpt2=0, **kwargs):
pass
kwargs = {"argOpt1" : 123, "argOpt2" : 234}
function_name('argument1', kwargs)
Look at this
and this
If the arguments that are to have the same value are contiguous, then:
def function_name(arg1=0,arg2=0,arg3=0,arg4=0):
print(arg1, arg2, arg3, arg4)
function_name(2, *[1] * 3)
Prints:
2 1 1 1
If you didn't write that function, and you can't change its signature, you can pass the right number of arguments that way :
function_name(*(0,) * function_name.__code__.co_argcount)
It will pass the same value (in that case 0) to all the arguments the function expects.
If you need to pass only 10 arguments for instance, you can write :
function_name(*(0,) * 10)
If you want to target specific arguments by their position and not their names, you can use this :
import inspect
def func(a, b, c=None, d='foo', e=False):
print(a, b, c, d, e)
def fill_args(fun, value, arg_numbers):
return {
name: value
for name in (
tuple(inspect.signature(fun).parameters)[i]
for i in arg_numbers
)
}
if __name__ == '__main__':
func(**fill_args(func, 0, (0, 1, 2)))
func(**fill_args(func, 'bar', (0, 1, 4)))
func(**fill_args(func, True, (0, 1, 3)))
What's the best way NOT to have to extracts the SAME KWARGS twice: once in the decorator (wrapper function) and once in the function itself.
Here is the function:
#close_logger
def close(**kwargs):
""" returns close object"""
# Get params
session_attributes = kwargs.get('sessionAttrbiutes', {})
message = kwargs.get('message', '')
LID = kwargs.get('LIData', {})
SANS = kwargs.get('SANS', [])
FS = kwargs.get('fulfillmentState', 'Fulfilled')
response = {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': SANS,
'fulfillmentState': FS,
'message': {
'contentType': LID,
'content': message
}
}
}
return response
and here is the decorator (used for logging the close event):
def close_logger(func):
#functools.wraps(func)
def wrapper(**kwargs):
# Get params
session_attributes = kwargs.get('sessionAttrbiutes', {})
message = kwargs.get('message', '')
LID = kwargs.get('LIData', {})
SANS = kwargs.get('SANS', [])
FS = kwargs.get('fulfillmentState', 'Fulfilled')
logger.debug('Logging:\n Function:{} Session Attributes: {}\n \
Message{}\n: LID: {}\n SANS: {}\n FS: {}'.format(
func.__name__,
session_attributes,
message,
LID,
SANS,
FS
))
return func(**kwargs)
return wrapper
Start by making your keyword arguments explicit for the close function.
def close(sessionAttrbiutes=None, message='', LIData=None, SANS=None, fulfillmentState='Fulfilled'):
if sessionAttrbiutes is None:
sessionAttrbiutes = {}
...
Note that I used None as default values for the mutable default values to avoid a common pitfall.
Then in your decorator use inspect.getfullargspec, similar to this answer:
import inspect
import functools
def get_default_args(func):
"""
returns a dictionary of arg_name:default_values for the input function
"""
argspec = inspect.getfullargspec(func)
return dict(zip(reversed(argspec.args), reversed(argspec.defaults)))
def close_logger(func):
#functools.wraps(func)
def wrapper(**kwargs):
kwargs_local = get_default_args(func)
kwargs_local.update(kwargs)
logger.debug("""Logging:
Function: {}
Session Attributes: {sessionAttrbiutes}
Message: {message}
LID: {LIData}
SANS: {SANS}
FS: {fulfillmentState}""".format(func.__name__, **kwargs_local))
return func(**kwargs)
return wrapper
This will raise a KeyError if one of those fields is not defined (either in the signature or the passed keywords). Of course it will show None for those where that is the default in the signature.
The full argspec will also contain positional arguments, so you might be able to figure those out as well.
**-notation can be used within function argument lists to both pack and unpack arguments.
**-notation within a function definition collects all the values into a dictionary.
>>> def kwargs_in_definition(**kwargs): return kwargs
>>> kwargs_in_definition(arg1 = 1, arg2 = 2, arg3 = 3)
{'arg1': 1, 'arg2': 2, 'arg3': 3}
**-notation within a function call unpacks all the values as keyword argumnts.
def kwargs_in_call(arg1 =0, arg2 = 0, arg3 = 0): return arg1, arg2, arg3
So when you pass kwargs (kwargs := {'arg1': 1, 'arg2': 2, 'arg3': 3}) to kwargs_in_call you get this:
>>> kwargs_in_call(kwargs)
({'arg1': 1, 'arg2': 2, 'arg3': 3}, 0, 0) # kwargs is used as arg1.
...but if you unpack it first:
>>> kwargs_in_call(**kwargs)
(1, 2, 3) # kwargs unapcks to "arg1 = 1, arg2 = 2, arg3 = 3"
All of this applies for * as well
Your specific case:
**kwargs in a function defintion gives you a regular old dict. You can turn that dictionary into a list -- however you wish to do that --, unpack that list and pass it on to another function. You'll have to account for ordering though, so it's difficult to make this work for all functions.
I'm not sure why you insist on your decorator function taking keyword arguments while your decorated function doesn't. You can, of-course, do whatever you want, but I would argue against such an approach, because you're decorator function is changing how your actual function behaves.
If you manage to sort those keyword arguments in to a valid argument list you'll be good.
Hope this helps.
EDIT: Oh, I forgot to point out that you're unpacking **kwargs in your decorator function (return func(**kwargs)) and then you're repacking them in your actual function (def close(**kwargs):. I'll be honest, that's a silly thing to do.
EDIT: You can use inspect.getargspec or inspect.signature to get a functions argument list. If you just have your decorator take in *args, you can match each value to a corresponding name from the argument list.
I am not able to handle to pass optional parameters in python **kwargs
def ExecuteyourQuery(self, queryStatement, *args, **kwargs):
if self.cursorOBJ is not None:
resultOBJ = self.cursorOBJ.execute(queryStatement, *args,**kwargs)
self.resultsVal = resultOBJ.fetchall()
The below statement works fine when I am calling the function
ExecuteyourQuery('select * from item_table where x = :x and y:y', x, y)
But when I give an extra argument which is not mentioned in the query, like
ExecuteyourQuery('select * from item_table where x = :x and y:y', x, y, z)
It returns 0 as output
When using args as the last parameter of your function, you can pass any number of arguments after the formal arguments, when existing. Args is a tuple.
def my_method(farg, *args):
print('FARG: ', farg)
print('ARGS: ', args)
my_method('Formal Argument', 1, 2, 3)
When using kwargs as the last parameter of your function, you can pass any number of named arguments after the formal arguments, when existing. Kwargs is a dictionary.
def my_method(farg, **kwargs):
print('FARG: ', farg)
print('KWARGS: ', kwargs)
my_method('Formal Argument', kwarg_1 = 1, kwarg_2 = 2, kwarg_3 = 3)
When using both args and kwargs as the last parameteres of your function, you can pass any number of arguments (that will be mapped to args) and named arguments (that will be mapped to kwargs) after the formal arguments, when existing.
def my_method(farg, *args, **kwargs):
print('FARG: ', farg)
print('ARGS: ', args)
print('KWARGS: ', kwargs)
my_method('Formal Argument', 1, 2, 3, kwarg_1 = 1, kwarg_2 = 2, kwarg_3 = 3)
Try this out and see the results in your console, hopefully it will help you solve your original problem :)
another way to do this is by setting a default value for a parameter
def method(one, two, three=3):
print(one)
print(two)
if three != 3: # don't have to use it like this but it is a default value
print(three)
this set a default value if the parameter is not filled
if it is filled it will overrule the default value and use that
method(11, 22) # prints first two parameters
method(23, 54, 89) # prints all three
Not sure if this is what your looking for but it is another way
I have a functions module that has some functions all with some common inputs, and others that are particular to them. e.g.
def func_a(time_series, window ='1D'):
def func_b(time_series, window ='1D', availability_history ):
def func_c(time_series, window ='1D', max_lag=25, use_probability ='T'):
I am trying to run these functions in a loop as follows:
func_list = [func_a, func_b, func_c]
windows = ['1D', '5D']
params = ['', hist, (25, 'T')]
for i_func, func in enumerate(func_list):
class_obj = class_X(A,B,func)
for window in windows:
args = (window, params[i_func]) # params is a list or tuple of other params for funcs e.g.
class_obj.run_func(args)
And in another module
class class_X(object):
def __init__(self, a, b, func_to_run):
self.a = a
self.ts = b
self.method = func_to_run
def generate_output(self, *args):
return self.method(self.ts, args) # time series is common and fixed for all, other params differ or change
The above code wouldn't work because I think the functions that I am calling need to be changed to make use of *argsrather than having fixed defined params.
I think *args is meant for functions where number of input params are not known, but I am trying to use it in a case where the number of input params is known, but varies across different functions in a loop.
Is there any fix for this where I don't have to modify the functions module and can still pass all the required params as a single object (e.g. list or tuple)?
EDIT-
macromoonshine's answer states I can use kwargs like this:
def generate_output(self, **kwargs):
return self.method(self.ts, kwargs)
With this modification you can call generate_outputs() as follows:
x.generate_outputs( window ='1D', max_lag=25, use_probability ='T')
where xis an instance of your class X
Can this be enhanced so I can pass args other than time_series and window as a lookup value in a loop e.g.
x.generate_outputs( window ='1D', params[iloop])
where
params[iloop] = max_lag=25, use_probability ='T'
I tried doing this:
params = (30, "F")
x.generate_outputs( window, *params)
but get an error
TypeError: generate_output() takes 1 positional argument but 4 were given
You can use the **kwargs instead which allows arbitrary keyword parameters. This should be easier than chinging each function. You have just to modify your generate_outputs() method in your code:
def generate_output(self, **kwargs):
return self.method(self.ts, kwargs)
With this modification you can call generate_outputs() as follows:
x.generate_outputs(time_series, window ='1D', max_lag=25, use_probability ='T')
where xis an instance of your class X.
If you want to pass the kwargs from a dict instead named parameter, you have to prefix the dictionary variable with **. The adapted code should look like this:
params = [{max_lag: 35, use_probability: 'F'}, ... ]
TS= [1,2,3,4]
for i_func, func in enumerate(func_list):
class_obj = class_X(TS, func)
for window in windows:
req_args = dict(params[i_func])
req_args['window'] = 0
class_obj.generate_output(**req_args)
I am struggling to pass a list of functions with a list of corresponding parameters. I also checked here, but it wasn't very helpful. for example (a naive approach which doesn't work):
def foo(data, functions_list, **kwarg):
for func_i in functions_list:
print func_i(data, **kwarg)
def func_1(data, par_1):
return some_function_1(data, par_1)
def func_2(data, par_2_0, par_2_1):
return some_function_2(data, par_2_0, par_2_1)
foo(data, [func_1, func_2], par_1='some_par', par_2_0=5, par_2_1=11)
Importantly, par_1 cannot be used in func_2, so each function consumes a unique set of parameters.
You could use the function's name as the keyword arguments. When indexing kwargs, you'd use func_i.__name__ as the key.
def foo(data, function_list, **kwargs):
for func_i in function_list:
print(func_i(data, kwargs[func_i.__name__]))
And now,
foo(data, [func_1, func_2], func_1='some_par', func_2=[5, 11])
You could use inspect.getargspec (I assume you use Python 2, you shouldn't use that function in Python 3 because it has been deprecated) to find out which argument names a function has and build a new dictionary based on those:
import inspect
def foo(data, functions_list, **kwargs):
for func_i in functions_list:
newkwargs = {name: kwargs[name]
for name in inspect.getargspec(func_i).args
if name in kwargs}
print(func_i(data, **newkwargs))
def func_1(data, par_1):
return data, par_1
def func_2(data, par_2_0, par_2_1):
return data, par_2_0, par_2_1
>>> data = 10
>>> foo(data, [func_1, func_2], par_1='some_par', par_2_0=5, par_2_1=11)
(10, 'some_par')
(10, 5, 11)
But a better way would be to simply associate parameters with functions that doesn't rely on introspection.
If you want to keep the foo function with that exact same declaration and you don't mind each function receiving the whole set of parameters you could do it like this:
You just need to add to each 'my_*' function the **kwargs parameter.
def foo(data, functions_list, **kwargs):
for my_function in functions_list:
print(my_function(data, **kwargs))
def my_sum(a, b, **kwargs):
return a + b
def my_sub(a, c, **kwargs):
return a - c
foo(0, [my_sum, my_sub], b=3, c=10)
Python automatically parses kwargs setting the b and c parameters where it has the value.
Another approach can be like this:
def foo(data, function_list, **kwargs):
function_dict = {
"func_1": func_1,
"func_2": func_2
}
for func_i in function_list:
print function_dict[func_i](data, **kwargs)
def func_1(data, **arg):
filtered_argument = {key: value for key, value in arg.items() if key.startswith('par_1')}
return list([data, filtered_argument])
def func_2(data, **arg):
filtered_argument = {key: value for key, value in arg.items() if key.startswith('par_2_')}
return list([data, filtered_argument])
data = [1,2,3]
foo(data, ['func_1', 'func_2'], par_1='some_par', par_2_0=5, par_2_1=11)
Output:
[[1, 2, 3], {'par_1': 'some_par'}]
[[1, 2, 3], {'par_2_0': 5, 'par_2_1': 11}]
I am sure that you can improvise your current code as it gets ugly in this way.
I like #COLDSPEED's approach, but want to present yet another solution. Pass always 3 values: function, args, keyword args:
Usage:
foo(
func_1, ('some_par',), {},
func_2, (5, 11), {},
)
Implementation (Python3 syntax):
def foo(*args3):
while args3:
func, args, kwargs, *args3 = args3
func(*args, **kwargs)
An approach would be making the 3rd argument of foo a positional argument and pass in a list of args with functions list:
def foo(data, functions_list, args):
for func, arg in zip(functions_list, args):
print(func(data, arg))
def func1(data, par_1):
return 'func1 called with {}'.format(par_1)
def func2(data, par_2):
return 'func2 called with {}'.format(par_2)
foo('some_data', [func1, func2],
[
{'par_1_1': 11, 'par_1_2': 12},
{'par_2_1': 21, 'par_2_2': 22}
])
zip() is used to map each function with the corresponding args.
Output:
func1 called with {'par_1_1': 11, 'par_1_2': 12}
func2 called with {'par_2_1': 21, 'par_2_2': 22}
You can do it something like that, "close" each parameters for function in a list item and then let "foo" split it backwards:
def foo(data, functions_list, kwarg):
for func_i, args in zip(functions_list, kwarg):
func_i(data, **args)
def func_1(data, par_1):
print("func_1 %s %s" % (data, par_1))
def func_2(data, par_2_0, par_2_1):
print("func_2 %s "
"%s %s" % (data, par_2_0, par_2_1))
data = "Some Data"
foo(data, [func_1, func_2], [{"par_1":'some_par'}, {"par_2_0":5, "par_2_1":11}])