0

I'm currently learning Python and trying to simplify my code. My scenerio is like this:

  • class "Person" contains 50 functions with different names
  • in class "Bla" i want to call those 50 functions based on entries in a dict, but am trying to avoid coding 50 different cases (if... elif...else)
  • my thought is to have one function in class "Bla" that decides which function of class "Person" to execute, based on an argument (my functionname in dict)
class Person:
    def walk(self):
        print('do something')

    def wink(self):
        print('do something else')

class Bla:
    def abstract_function(data):
        for key in data:
            # execute function in class Person
            p = Person()
            # this is where i need help - how to execute the function of "Person" based on the entry in data?
            p.key()

Giova
  • 1,879
  • 1
  • 19
  • 27
paaax
  • 144
  • 1
  • 1
  • 8
  • Depending on `data`, just call `walk` or `wink` as necessary. You almost got it. – 1737973 Aug 18 '19 at 05:27
  • 1
    Possible duplicate of [Calling a function of a module by using its name (a string)](https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string) – Susmit Agrawal Aug 18 '19 at 05:27
  • Are the keys the names of methods or something else? I.e. are they „walk“, „wink“ or are they „feet“, „eyes“? – MisterMiyagi Aug 18 '19 at 05:49
  • @SusmitAgrawal **not a duplicate** cause even if the solution is similar the question is different for the nature of the items involved `modules != objects`. – Giova Aug 18 '19 at 07:08

2 Answers2

3

You can use getattr(object, method). This will retrieve the method of a given name from your object. In your case

class Person:
    def walk(self):
        print('do something')

    def wink(self):
        print('do something else')

class Bla:
    def abstract_function(data):
        for key in data:
            p = Person()
            getattr(p, key)()

getattr does not call the method so you have to add parentheses to do that.

benrussell80
  • 297
  • 4
  • 10
2

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)), ...).

Giova
  • 1,879
  • 1
  • 19
  • 27