Search code examples
pythonruntimeintrospectionclass-method

Find name of dynamic method in Python


I want to proxy an API over a network. I have the API in a dictionary. I'd like to create a class with the API methods from the dictionary so I can use the API as if I was local. The trouble is finding the name of my dynamically created method. (My approach is based on Adding a Method to an Existing Object and Python dynamic class methods.)

class MainClass(object):
    def build_API(self):
        methods = dict(meth1='arg1', meth2='arg2')
        for key in methods.iterkeys():
            setattr(self, key, MethodType(self.default_API, self))
    def default_API(self, *args, **kwargs)
        called_as_name = ????
        self.send_message(called_as_name, args, kwargs)
    def send_message(self, called_as_name, *args, **kwargs)
        ...
        # Send API command over network
        ....

To use this:

api = MainClass()
api.build_API()
api.meth1()

However, everything I try for "called_as_name" always returns "default_API" and never "meth1". How can I get "called_as_name = meth1" when I type "api.meth1()" and "called_as_name = meth2" when I type "api.meth2()"?

I have tried:

curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
called_as_name = calframe[1][3]

from Python: How to get the caller's method name in the called method?

called_as_name = inspect.stack()[1][5]

from Getting the caller function name inside another function in Python?

called_as_name = sys._getframe(1).f_code.co_name

from Getting the caller function name inside another function in Python?


Solution

  • Trying to do this with actual methods and grabbing the names from the stack frame with that sort of introspection trickery is a recipe for disaster. Instead, make the "methods" be custom callable objects that know their names. Here's a sketch:

    class FakeMethod(object):
        def __init__(self, name, parent):
            self.name = name
            self.parent = parent
    
        def __call__(self, *args, **kwargs):
            self.parent.send_message(self.name, args, kwargs)
    
    class MainClass(object):
        def build_API(self):
            methods = dict(meth1='arg1', meth2='arg2')
            for key in methods.iterkeys():
                setattr(self, key, FakeMethod(key, self))
        def send_message(self, called_as_name, *args, **kwargs):
            print("Sending message:", called_as_name, args, kwargs)
    

    Then:

    >>> api = MainClass()
    >>> api.build_API()
    >>> api.meth1()
    Sending message: meth1 ((), {}) {}
    >>> api.meth2()
    Sending message: meth2 ((), {}) {}
    

    In theory you could even use __getattr__ on the MainClass to dynamically generate a FakeMethod every time an attribute name is accessed that is not defined but is listed in some list of API method names.