I advice you to check input before trying to run something potentially mispelled, or absent. The input dict may even contain args and kwargs as values in a tuple like in the following example:
actions = dict(
walk=(tuple(), dict(steps=10, step_size=50)),
wink=((1, 2, 3), dict(a=0)),
invalid=tuple(),
absent=(tuple(), None),
)
Assuming the Person be like:
class Person:
def walk(self, step_size=0, steps=0):
print(f'Walking: {steps} steps of size {step_size}')
def wink(self, x, y, z, a):
print(f'Wink with {x}, {y}, {z} and a {a}')
With the above Person and the dict built that way you can then write Bla as follows:
class Bla:
availables = {'walk', 'wink', 'absent'}
def forbidden(self, *args, **kwargs):
print(f'{self.name}() is forbidden')
def __init__(self):
self.name = ''
def run(self, actions: dict):
p = Person()
for m, args in actions.items():
self.name = m
if len(args) != 2: continue
kwargs = args[1] or dict()
args = args[0] or tuple()
if m in Bla.availables:
method = getattr(p, m, self.forbidden)
try:
method(*args, **kwargs)
except (TypeError, ValueError, Exception) as e:
print(f'{str(e)} --> {self.name}({args}, {kwargs}')
Running that code you'll get:
Walking: 10 steps of size 50
Wink with 1, 2, 3 and a 0
absent() is forbidden
Hera are a couple of things worth mentioning:
The third argument to getattr is the default value returned in case the given attr is not present in the given object.
By compiling Bla.availables you can dinamically (i.e. changing during runtime) filter the members you want to be available for calling.
By using exception handling around the call to the method allows avoiding program crashes on wrong input to methods.
Having a dict as input would not allow you to call the same method more than once on a given Person object. If that is an issue for you I advice the use of tuples of tuples of tuples and dict like ('wink'((1, 2, 3) dict(a=0)), ...).