0

I am trying to create a dynamic method executor, where I have a list that will always contain two elements. The first element is the name of the file, the second element is the name of the method to execute.

How can I achieve this?

My below code unfortunately doesn't work, but it will give you an good indication of what I am trying to achieve.

from logic.intents import CenterCapacity

def method_executor(event):
    call_reference = ['CenterCapacity', 'get_capacity']

    # process method call
    return call_reference[0].call_reference[1]

Thanks!

3 Answers3

0

starting from:

from logic.intents import CenterCapacity

def method_executor(event):
    call_reference = ['CenterCapacity', 'get_capacity']

    # process method call
    return call_reference[0].call_reference[1]

Option 1

We have several options, the first one is using a class reference and the getattr. For this we have to remove the ' around the class and instantiate the class before calling a reference (you do not have to instantiate the class when the method is a staticmethod.)

def method_executor(event):
    call_reference = [CenterCapacity, 'get_capacity']  # We now store a class reference

    # process method call
    return getattr(call_reference[0](), call_reference[1])

option 2

A second option is based on this answer. It revolves around using the getattr method twice. We firstly get module using sys.modules[__name__] and then get the class from there using getattr.

import sys

def method_executor(event):
    call_reference = ['CenterCapacity', 'get_capacity'] 
    class_ref = getattr(sys.modules[__name__], call_reference[0])
    return getattr(class_ref, call_reference[1])

Option 3

A third option could be based on a full import path and use __import__('module.class'), take a look at this SO post.

Thymen
  • 2,089
  • 1
  • 9
  • 13
  • I rarely use classes for the different intents I have in my code, this is because my code is being executed once per request. Everytime an end user has an intent with the chatbot (what I am working on), AWS lambda will execute my code. –  Dec 04 '20 at 09:36
0

You can use __import__ to look up the module by name and then then use getattr to find the method. For example if the following code is in a file called exec.py then

def dummy(): print("dummy")

def lookup(mod, func):
    module = __import__(mod)
    return getattr(module, func)

if __name__ == "__main__":
    lookup("exec","dummy")()

will output

dummy

Addendum

Alternatively importlib.import_module can be used, which although a bit more verbose, may be easier to use.

The most important difference between these two functions is that import_module() returns the specified package or module (e.g. pkg.mod), while __import__() returns the top-level package or module (e.g. pkg).

def lookup(mod, func):
    import importlib
    module = importlib.import_module(mod)
    return getattr(module, func)
Thomas
  • 4,980
  • 2
  • 15
  • 30
  • I assume the module in the lookup function needs to be the whole path to the file? For example: ```lookup("config.intents.exec", "dummy")``` Then it would call "dummy" inside the file "exec", right? Also, If I need to call a method inside a class, should I then pass the module as : ```lookup("package.file.class", "dummy")```? –  Dec 04 '20 at 09:36
  • you can read up on the details of [`__import__`](https://docs.python.org/3/library/functions.html?highlight=__import__#__import__), it can be configured. – Thomas Dec 04 '20 at 12:09
  • i also added an variant using [`importlib.import_module`](https://docs.python.org/3/library/importlib.html?highlight=__import__#importlib.import_module) which is better suited to retrieving sub-modules – Thomas Dec 04 '20 at 12:26
  • Thanks. I got it to work with the importlib module! Excellent. –  Dec 04 '20 at 13:55
0

(Note: This answer assumes that the necessary imports have already happened, and you just need a mechanism to invoke the functions of the imported modules. If you also want the import do be done by some program code, I will have to add that part, using importlib library)

You can do this:

globals()[call_reference[0]].__dict__[call_reference[1]]()

Explanation:

globals() returns a mapping between global variable names and their referenced objects. The imported module's name counts as one of these global variables of the current module.

Indexing this mapping object with call_reference[0] returns the module object containing the function to be called.

The module object's __dict__ maps each attribute-name of the module to the object referenced by that attribute. Functions defined in the module also count as attributes of the module.

Thus, indexing __dict__ with the function name call_reference[1] returns the function object.

fountainhead
  • 3,584
  • 1
  • 8
  • 17
  • Good idea! It's also quite simple. Wouldn't it be better to use importlib, and in case yes, how would this be done? –  Dec 04 '20 at 09:31