0

From a series of string, I am trying to call methods inside a class. Unfortunately, The method is not called properly because it needs the self to indicate it is internal to the class. How can I fix that logic?

class SomeClass(object):
    def apply(self):
        rules = [{'action': 'replace'}, {'action': 'split'}, {'action': 'remove'}]
        return [eval('_perform_' + r['action'])() for r in rules
              if r['action'] in ['replace', 'split', 'remove']]

    def _perform_replace(self):
        print("performing replace")

    def _perform_split(self):
        print("performing split")

    def _perform_remove(self):
        print("performing remove") 

SomeClass().apply()

This throws following exception:

NameError: name '_perform_replace' is not defined

Star
  • 3,222
  • 5
  • 32
  • 48
Michael
  • 2,436
  • 1
  • 36
  • 57
  • If you want to access `staticmethods` you have to use a `decorater` to define the class as a static member function. Then you call it like `SomeClass.apply()` without the `()`. Furthermore if you are trying to split/escape strings in python, there are much easier ways than what you are trying to do. – user1767754 Dec 21 '17 at 07:56
  • Could you elaborate? I know there are simple methods but here my `rules` list is actually a json file with several domain-specific rules. - The replace/split/remove are more involved and require interactions with pandas data frames. I did not explain that to focus on the core of my problem. – Michael Dec 21 '17 at 08:01
  • 1
    You can access the methods like attributes of the object using getattr. So from inside your apply method you could use `getattr(self, '_perform_' + r['action'])()` – antonagestam Dec 21 '17 at 08:14
  • @antonagestam thanks, is this safer than the option provided by the accepted answer? – Michael Dec 21 '17 at 08:30
  • 1
    @Michael The current accepted answer does exactly that only wraps it in a static method. I would say there's no difference in safety since both are using `getattr`. It's always a good idea to avoid using eval. :) – antonagestam Dec 21 '17 at 09:11
  • 1
    @Michael I think this is a really good example of how functions are first class citizens in Python. Methods are just functions that are called with their instance as first argument. And methods are attributes of the class/instance. – antonagestam Dec 21 '17 at 09:13

2 Answers2

3

You should use self to invoke the instance methods. So, change your apply function to

  def apply(self):
        rules = [{'action': 'replace'}, {'action': 'split'}, {'action': 'remove'}]
        return [eval('self._perform_' + r['action'])() for r in rules
              if r['action'] in ['replace', 'split', 'remove']]

Note: Using eval is a bad practice. You can find the reason here

You can make use of getattr instead.

For example(this example is just to illustrate how getattr is working)

class SomeClass(object):
    def apply(self):
        method_to_be_called = "_perform_replace"
        SomeClass.method_caller(method_to_be_called)(self)
        # Invoke like this if you want your function to accept args.
        # SomeClass.method_caller(method_to_be_called, args1, args2,...)(self)

    def _perform_replace(self):
        print("performing replace")

    def _perform_split(self):
        print("performing split")

    def _perform_remove(self):
        print("performing remove")

    @staticmethod
    def method_caller(name_, *args, **kwargs):
        def caller(obj):
            return getattr(obj, name_)(*args, **kwargs)

        return caller
Abdul Niyas P M
  • 18,035
  • 2
  • 25
  • 46
  • Thanks for that, could you propose an alternative to `eval` that will be less risky for my example? – Michael Dec 21 '17 at 08:02
  • thanks, if `apply` needs to pass parameters to the `_perform` methods - those parameters will be passed like so? `SomeClass.method_caller(method_to_be_called)(self, a, b)` – Michael Dec 21 '17 at 08:22
  • 1
    I accepted your answer as it provides a direct fit in my current logic but also provide a better solution! Thanks for this great support. – Michael Dec 21 '17 at 08:27
  • @Michael Correction: If `apply` needs to pass parameters to the `_perform` methods - those parameters should be passed like `SomeClass.method_caller(method_to_be_called, a, b)(self)` not `SomeClass.method_caller(method_to_be_called)(self, a, b)`. – Abdul Niyas P M Dec 21 '17 at 08:53
0

Your example is a little involved, but if you want to call functions based on some logic, you can just use the function like a pointer. Here is an example:

class SomeClass(object):
    @staticmethod
    def apply():
        rules = [{'action':SomeClass.test()}]
        return rules[0]['action']

    @staticmethod
    def test():
        print("test")


SomeClass.apply()
>test

I'm not sure if you are familiar with staticmethods but if your functions can live at their own, you can decorate your functions to be static to call them from anywhere.

user1767754
  • 23,311
  • 18
  • 141
  • 164