在Python中优雅地实现MixIns的方法有哪些?

7 投票
2 回答
6348 浏览
提问于 2025-04-15 22:17

我需要找到一种优雅的方法来实现两种 MixIn。

第一种:

class A(object):
    def method1(self):
        do_something()

现在,一个 MixInClass 应该让 method1 这样做:do_other() -> A.method1() -> do_smth_else(),也就是说,基本上是“包装”旧的函数。我相信一定有好的解决方案。

第二种:

class B(object):
    def method1(self):
        do_something()
        do_more()

在这种情况下,我希望 MixInClass2 能够在 do_something()do_more() 之间插入自己,也就是:do_something() -> MixIn.method1 -> do_more()。我明白这可能需要修改 class B,这没问题,只是想找最简单的方法来实现这个。

这些问题其实都很简单,我实际上已经解决了,但我的解决方案不太优雅。

第一个问题是通过使用 self._old_method1 = self.method1(); self.method1() = self._new_method1(); 来解决的,然后写了一个 _new_method1() 来调用 _old_method1()

问题是:多个 MixIn 都会重命名为 _old_method1,这样就显得不优雅了。

第二个 MixIn 的问题是通过创建一个虚拟方法 call_mixin(self): pass 来解决的,并在调用之间插入它,定义 self.call_mixin()。同样,这也不优雅,并且在多个 MixIn 的情况下会出问题。

有什么想法吗?


多亏了 Boldewyn,我找到了第一个问题的优雅解决方案(我忘了可以动态创建装饰器,而不需要修改原始代码):

class MixIn_for_1(object):
    def __init__(self):
        self.method1 = self.wrap1(self.method1)
        super(MixIn_for_1, self).__init__()

    def wrap1(self, old):
        def method1():
            print "do_other()"
            old()
            print "do_smth_else()"
        return method1

我仍在寻找第二个问题的想法(这个想法不适用,因为我需要在旧方法内部插入,而不是像这种情况那样在外部)。


第二个问题的解决方案在下面,将 "pass_func" 替换为 lambda:0

2 个回答

5

我觉得,这个问题可以用一种很“Python风”的方式来解决,方法是使用装饰器。(还有PEP 318

3

这里有另一种实现 MixinClass1MixinClass2 的方法:

装饰器在你需要包装很多函数的时候很有用。不过因为 MixinClass1 只需要包装一个函数,我觉得用猴子补丁(monkey-patch)会更清晰:

MixinClass1 中使用双下划线来命名 __old_method1__method1 是很有用的。因为 Python 有一种命名混淆的规则,使用双下划线可以把这些属性限制在 MixinClass1 内部,这样你就可以在其他混入类中使用相同的属性名称,而不会引起不必要的名称冲突。

class MixInClass1(object):
    def __init__(self):
        self.__old_method1,self.method1=self.method1,self.__method1
        super(MixInClass1, self).__init__()        
    def __method1(self):
        print "pre1()"
        self.__old_method1()
        print "post1()"

class MixInClass2(object):
    def __init__(self):
        super(MixInClass2, self).__init__()        
    def method1_hook(self):
        print('MixIn method1')

class Foo(MixInClass2,MixInClass1):
    def method1(self):
        print "do_something()"
        getattr(self,'method1_hook',lambda *args,**kw: None)()
        print "do_more()"

foo=Foo()
foo.method1()

撰写回答