动态调用重写函数

时间:2014-08-10 13:51:54

标签: python oop python-2.7

我遍历列表并希望在每个项目上调用一个函数,但此函数应该是可替换的。

例如,我有以下脚本:

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

前两个exec_all确实可以正常工作,但接下来的两个不能正常工作,因为它会尝试拨打ChildA.a中不存在的ChildB。最后两个提升了NotImplementedError

看起来应该是这样的:

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

那么我如何支持Parent的多个子类?

2 个答案:

答案 0 :(得分:2)

你是否经历过这样的事情?

exec_all(lambda x: x.a())

或:

def call_a(obj):
    return obj.a()
exec_all(call_a)

答案 1 :(得分:0)

传递方法名称,而不是方法。使用getattr(child, methodname)获取方法:

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) 

产量

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

在Python2中,ChildA.a未绑定的方法。与Python3不同,未绑定方法检查第一个参数是否是正确类的实例 - 在本例中为ChildA。这就是调用

的原因
ChildA.a(ChildB(), text)

提出TypeError

TypeError: unbound method a() must be called with ChildA instance as first argument (got ChildB instance instead)

在Python3中,这样的调用是可以的,但是如果你这样做,那么将所有这些方法设置为普通函数而不是方法可能会更好。


听起来你真的想在发布它们时保留函数调用的形式:

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) 

如果我们将此作为固定要求,那么您可以通过定义exec_all来获得所需的行为,如下所示:

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) 

但故意传递错误的方法并不是一个好的设计。如果希望调用ChildA.a,则不应传递ChildB.a。 Python并不简单,因为这不是OOP的工作方式。 传递正确的方法或将方法名称作为字符串传递(如上所示)是更好的选择。