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