Reputation: 2379
I iterate through a list and want to call a function on each item, but this function should be replaceable.
For example I have the following script:
class Parent(object):
def a(self, text):
raise NotImplementedError("called Parent.a")
def b(self, text):
raise NotImplementedError("called Parent.b")
class ChildA(Parent):
def a(self, text):
return "A.a: {}".format(text)
def b(self, text):
return "A.b: {}".format(text)
class ChildB(Parent):
def a(self, text):
return "B.a: {}".format(text)
def b(self, text):
return "B.b: {}".format(text)
# the separation is ONLY so that the first exec_all doesn't fail
# in my production code it's a list of mixed instances
children = [
ChildA(), # obviously here might be several different ChildA instances
]
childrenMixed = children + [
ChildB(), # obviously here might be several different ChildB instances
]
def exec_all(method, children):
for child in children:
try:
print(method(child, "Hello world"))
except Exception as e:
print("Unable to call method for child '{}': {}".format(child, e.message))
exec_all(ChildA.a, children) # works
exec_all(ChildA.b, children) # works
exec_all(ChildA.a, childrenMixed) # TypeError
exec_all(ChildA.b, childrenMixed) # TypeError
exec_all(Parent.a, childrenMixed) # NotImplementError
exec_all(Parent.b, childrenMixed) # NotImplementError
The first two exec_all
does work fine, but the next two don't work, because it tries to call ChildA.a
which doesn't exists in ChildB
. And the last two raise the NotImplementedError
.
It should look something like this:
A.a: Hello world # 1st exec_all
A.b: Hello world # 2nd exec_all
A.a: Hello world # 3rd exec_all
B.a: Hello world # but TypeError
A.b: Hello world # 4th exec_all
B.b: Hello world # but TypeError
A.a: Hello world # 5th exec_all
B.a: Hello world # but NotImplementError
A.b: Hello world # 6th exec_all
B.b: Hello world # but NotImplementError
So how do I support multiple subclasses of Parent
?
Upvotes: 0
Views: 122
Reputation: 879361
Pass the method name, not the method. Use getattr(child, methodname)
to obtain the method:
class Parent(object):
def a(self, text):
raise NotImplementedError("called Parent.a")
def b(self, text):
raise NotImplementedError("called Parent.b")
class ChildA(Parent):
def a(self, text):
return "A.a: {}".format(text)
def b(self, text):
return "A.b: {}".format(text)
class ChildB(Parent):
def a(self, text):
return "B.a: {}".format(text)
def b(self, text):
return "B.b: {}".format(text)
children = [ ChildA(), ]
childrenMixed = children + [ ChildB(), ]
def exec_all(methodname, children):
for child in children:
method = getattr(child, methodname)
print(method("Hello world"))
print
exec_all('a', children)
exec_all('b', children)
exec_all('a', childrenMixed)
exec_all('b', childrenMixed)
exec_all('a', childrenMixed)
exec_all('b', childrenMixed)
yields
A.a: Hello world
A.b: Hello world
A.a: Hello world
B.a: Hello world
A.b: Hello world
B.b: Hello world
A.a: Hello world
B.a: Hello world
A.b: Hello world
B.b: Hello world
In Python2, ChildA.a
is an unbound method. Unlike Python3, unbound methods check if the first argument is an instance of the correct class -- in this case ChildA
. This is why calling
ChildA.a(ChildB(), text)
raises the TypeError
:
TypeError: unbound method a() must be called with ChildA instance as first argument (got ChildB instance instead)
In Python3, such a call would be okay, though if you are doing this it would probably be better to make all these methods plain functions rather than methods.
It sounds like you really want to preserve the form of your function calls as you posted them:
exec_all(ChildA.a, children)
exec_all(ChildA.b, children)
exec_all(ChildA.a, childrenMixed)
exec_all(ChildA.b, childrenMixed)
exec_all(Parent.a, childrenMixed)
exec_all(Parent.b, childrenMixed)
If we take this as a fixed requirement, then you could get the desired behavior by defining exec_all
as follows:
class Parent(object):
def a(self, text):
raise NotImplementedError("called Parent.a")
def b(self, text):
raise NotImplementedError("called Parent.b")
class ChildA(Parent):
def a(self, text):
return "A.a: {}".format(text)
def b(self, text):
return "A.b: {}".format(text)
class ChildB(Parent):
def a(self, text):
return "B.a: {}".format(text)
def b(self, text):
return "B.b: {}".format(text)
children = [
ChildA(),
]
childrenMixed = children + [
ChildB(),
]
def exec_all(method, children):
methodname = method.__name__
for child in children:
method = getattr(child, methodname)
print(method("Hello world"))
exec_all(ChildA.a, children)
exec_all(ChildA.b, children)
exec_all(ChildA.a, childrenMixed)
exec_all(ChildA.b, childrenMixed)
exec_all(Parent.a, childrenMixed)
exec_all(Parent.b, childrenMixed)
But passing the wrong method on purpose is not a good design. You shouldn't pass ChildA.a
when you want ChildB.a
to be called. Python does not make this simple because this is not the way OOP is intended to work.
Either passing the correct methods or passing method names as strings (as shown above) are better options.
Upvotes: 0
Reputation: 65791
Are you after something like this?
exec_all(lambda x: x.a())
or:
def call_a(obj):
return obj.a()
exec_all(call_a)
Upvotes: 2