I need to be able to dynamically invoke a method on a class that accepts various parameters based on the string name and a dictionary of variables. I know how to find the signature with the inspect module, and I can get the method with the getattr, but I do not know how to assign the parameters in the correct order to invoke it in a purely dynamic way.
class MyClass():
def call_me(a, b, *args, foo='bar', **kwargs):
print('Hey, I got called!')
command = {
'action':'call_me',
'parameters':{
'a': 'Apple',
'b': 'Banana',
'args':['one','two','three','four'],
'foo':'spam',
'clowns':'bad',
'chickens':'good'
}
}
me = MyClass()
action = getattr(me,command['action'])
... now what?
I need to be able to dynamically call this function as if this code were used, without any foreknowledge of the actual parameters for the method:
a = command['parameters']['a']
b = command['parameters']['b']
args = command['parameters']['args']
foo = command['parameters']['foo']
kwargs = {
'clowns': command['parameters']['clowns'],
'chickens':command['parameters']['chickens']
}
value = action(a, b, *args, foo=foo, **kwargs)
Surely there is a good pythonic way to do this.
Edit: Fixed getattr to call instance of MyClass instead of MyClass directly.
This is the best way I have found so far to capture every possible combination of normal args, *args, keyword args and **kwargs without getting any errors:
import inspect
class MyClass():
def a(self):
pass
def b(self,foo):
pass
def c(self,foo,*extras):
pass
def d(self,foo,food='spam'):
pass
def e(self,foo,**kwargs):
pass
def f(self,foo,*extras,food='spam'):
pass
def g(self,foo,*extras,**kwargs):
pass
def h(self,foo,*extras,food='spam',**kwargs):
pass
def i(self,*extras):
pass
def j(self,*extras,food='spam'):
pass
def k(self,*extras,**kwargs):
pass
def l(self,*extras,food='spam',**kwargs):
pass
def m(self,food='spam'):
pass
def n(self,food='spam',**kwargs):
pass
def o(self,**kwargs):
pass
def dynamic_invoke(obj,name,parameters):
action = getattr(obj,name)
spec = inspect.getfullargspec(action)
used = []
args = ()
kwargs = {}
for a in spec.args[1:]:
# skip the "self" argument since we are bound to a class
args += (parameters[a], )
used.append(a)
if spec.varargs:
args += tuple(parameters[spec.varargs])
used.append(spec.varargs)
for kw in spec.kwonlyargs:
try:
kwargs[kw] = parameters[kw]
used.append(kw)
except KeyError:
pass
# pass remaining parameters to kwargs, if allowed
if spec.varkw:
for k,v in parameters.items():
if k not in used:
kwargs[k] = v
return action(*args,**kwargs)
me = MyClass()
params = {
'foo':'bar',
'extras':['one','two','three','four'],
'food':'eggs',
'parrot':'blue'
}
dynamic_invoke(me,'a',params)
dynamic_invoke(me,'b',params)
dynamic_invoke(me,'c',params)
dynamic_invoke(me,'d',params)
dynamic_invoke(me,'e',params)
dynamic_invoke(me,'f',params)
dynamic_invoke(me,'g',params)
dynamic_invoke(me,'h',params)
dynamic_invoke(me,'i',params)
dynamic_invoke(me,'j',params)
dynamic_invoke(me,'k',params)
dynamic_invoke(me,'l',params)
dynamic_invoke(me,'m',params)
dynamic_invoke(me,'n',params)
dynamic_invoke(me,'o',params)
print('done!')
Try like this:
action = getattr(me,command['action'])
action(**{'a': 'Apple',
'b': 'Banana',
'args':['one','two','three','four'],
'foo':'spam',
'clowns':'bad',
'chickens':'good'
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With