Python inject a class based on a value passed to a function

Question:

Let’s say I have two classes which have methods accepting arguments as below:

class Foo:

    def __m1(*scenario):
        print("Foo.m1()")
        for s in scenario:
            print(s)

class Bar:

    def __m1(*scenario):
        print("Bar.m1()",scenario)

Now we create an interface class that will inherit those two classes.

class Interface(Foo, Bar):

    def __init__(self):
        self.servicer = None
        self.scenario = None
        super().__init__()

    def say_hello(self):
        print("Interface.say_hello")

It has its own attributes and methods. The servicer attribute will be set by the client to tell the interface whether it should use methods from Foo or Bar. All it takes is a little bit of getattr redirection:

def __getattr__(self, key: str):
    key2 = f"_{self.servicer}__{key}({self.scenario})" # _Bar__m1 or _Foo__m1.
    return self.__getattribute__(key2)

At this point, when we call interface.m1(), it will actually call interface._Foo__m1() or interface._Bar__m1().

Then the client is simply:

class Client:
    interface = Interface()

    def call_servicer(self, method_name, whichservicer):
        self.interface.servicer = whichservicer
        self.interface.method = method_name
        self.interface.m1() ## Need a way to avoid this

client = Client()
client.call_servicer("m1","Foo")
client.call_servicer("m1","Bar")
client.interface.say_hello()

Now, when I run the code, I see the response below:

Foo.m1()
<__main__.Interface object at 0x102fc1fa0>
Bar.m1() (<__main__.Interface object at 0x102fc1fa0>,)
Interface.say_hello
    
Process finished with exit code 0

In brief the problem I am trying to solve is as follows:

  • I would like to pass arguments to the method in class Foo or Bar using reflection
  • Also, I would like to avoid self.interface.m1() instead will like to pass the method name as a variable
Asked By: JavaMan

||

Answers:

It looks like you are trying to design an iterface. This article on Python interfaces should help.
My personal view – formal (ABC) and informal interfaces are sufficient to cover almost all cases. Use metaclasses only if you really understand why you cant’t use the former two.

Answered By: Jev

Let’s define your classes the same way as before except we want to mangle the methods names with two underscores:

class Foo:  

    def __m1(self):
        print("Foo.m1()")        
           

class Bar:   
 
    def __m1(self):
        print("Bar.m1()")

Now we create an interface class that will inherit those two classes.

class Interface(Foo, Bar):
    
    def __init__(self):
        self.servicer = None
        super().__init__()
    
    def say_hello(self):
        print("Interface.say_hello")

It has its own attributes and methods. The servicer attribute will be set by the client to tell the interface whether it should use methods from Foo or Bar. All it takes is a little bit of __getattr__ redirection:

    def __getattr__(self, key: str):
        key2 = f"_{self.servicer}__{key}" # _Bar__m1 or _Foo__m1.
        return self.__getattribute__(key2)

At this point, when we call interface.m1(), it will actually call interface._Foo__m1() or interface._Bar__m1().

Then you client is simply:

class Client:   
    interface = Interface()
    
    def call_servicer(self, whichservicer):
        self.interface.servicer = whichservicer
        self.interface.m1()

The test:

client = Client()
client.call_servicer("Foo")
client.call_servicer("Bar")
client.interface.say_hello()
# >>> Foo.m1()
# >>> Bar.m1()
# >>> Interface.say_hello
Answered By: Guimoute

The servicer attribute will be set by the client to tell the interface
whether it should use methods from Foo or Bar.

Stop right here. This should tell you that you don’t want to use inheritance here. The MRO – the method resolution order, is what takes care of that. That is essentially what inheritance is, it is delegating to the MRO how to resolve a message. You don’t want to do that so just don’t use inheritance. Don’t use inheritance, and then, go through a bunch of hijinks to subvert it.

What you are describing is essentially a mapping from a string to an object. In the simplest case, you could just probably use a map then, i.e. a dict:

class Foo:

    def m1(*scenario):  # Don't use double-underscores.
        print("Foo.m1()")
        for s in scenario:
            print(s)
class Bar:

    def m1(*scenario):
        print("Bar.m1()",scenario)

interface = {
    Foo.__name__ : Foo(),
    Bar.__name__ : Bar()
}

Then, to call use this client code, you would just do:

def call_servicer(self, whichservicer):
    self.interface[whichservicer].m1() ## Need a way to avoid this

Or if you needed to retrieve the method dynamically from a string:

def call_servicer(self, method, whichservicer):
    getattr(self.interface[whichservicer], method)()

Now, if interface really needs to be it’s own type, you can just wrap that logic up:

class Interface:
    def __init__(self):
        self.servicer = None
        self.dispatch = {
            Foo.__name__ : Foo()
            Bar.__name__ : Bar()
        }
    def say_hello(self):
        print("Interface.say_hello")

Then in the client:

def call_servicer(self, method, servicer, *args, **kwargs):
    return getattr(self.interface[servicer], method)(*args, **kwargs)
Answered By: juanpa.arrivillaga

Here is a new answer based on your updated question that doesn’t look anything like the initial one so the solution is much simpler. We can get rid of the interface class entirely.

class Foo:

    def m1(self, *scenario):
        print("Foo.m1()", scenario)



class Bar:

    def m1(self, *scenario):
        print("Bar.m1()", scenario)

            
         
class Client:
    
    foo = Foo()
    bar = Bar()
    
    def call_servicer(self, method_name: str, whichservicer: str, *args):
        servicer = getattr(self, whichservicer) # self.foo or self.bar
        method = getattr(servicer, method_name) # self.foo.m1 for example
        method(*args)    
        
        
# Test:   
client = Client()
client.call_servicer("m1", "foo")
client.call_servicer("m1", "bar")
client.call_servicer("m1", "bar", "with", "some", "arguments")
# >>> Foo.m1() ()
# >>> Bar.m1() ()
# >>> Bar.m1() ('with', 'some', 'arguments')
Answered By: Guimoute
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.