在Python中可以不使用继承实现mixin行为吗?

5 投票
8 回答
2896 浏览
提问于 2025-04-16 06:49

在Python中,有没有什么好的方法可以实现类似于Ruby中的mixin行为,也就是说,不使用继承的方式?

class Mixin(object):
    def b(self): print "b()"
    def c(self): print "c()"

class Foo(object):
    # Somehow mix in the behavior of the Mixin class,
    # so that all of the methods below will run and
    # the issubclass() test will be False.

    def a(self): print "a()"

f = Foo()
f.a()
f.b()
f.c()
print issubclass(Foo, Mixin)

我曾经模糊地想用类装饰器来实现这个,但我的尝试让我感到困惑。我在这个话题上的大部分搜索都指向了使用继承(或者在更复杂的情况下,使用多重继承)来实现mixin行为。

8 个回答

3

这个内容是基于在Ruby中实现的方式,正如Jörg W Mittag所解释的那样。在if __name__=='__main__'之后的那一大堆代码其实都是测试或演示用的代码。实际上,真正的代码只有13行。

import inspect

def add_mixins(*mixins):
    Dummy = type('Dummy', mixins, {})
    d = {}

    # Now get all the class attributes. Use reversed so that conflicts
    # are resolved with the proper priority. This rules out the possibility
    # of the mixins calling methods from their base classes that get overridden
    # using super but is necessary for the subclass check to fail. If that wasn't a
    # requirement, we would just use Dummy above (or use MI directly and
    # forget all the metaclass stuff).

    for base in reversed(inspect.getmro(Dummy)):
        d.update(base.__dict__)

    # Create the mixin class. This should be equivalent to creating the
    # anonymous class in Ruby.
    Mixin = type('Mixin', (object,), d)

    class WithMixins(type):
        def __new__(meta, classname, bases, classdict):
            # The check below prevents an inheritance cycle from forming which
            # leads to a TypeError when trying to inherit from the resulting
            # class.
            if not any(issubclass(base, Mixin) for base in bases):
                # This should be the the equivalent of setting the superclass 
                # pointers in Ruby.
                bases = (Mixin,) + bases
            return super(WithMixins, meta).__new__(meta, classname, bases,
                                                   classdict)

    return WithMixins 


if __name__ == '__main__':

    class Mixin1(object):
        def b(self): print "b()"
        def c(self): print "c()"

    class Mixin2(object):
        def d(self): print "d()"
        def e(self): print "e()"

    class Mixin3Base(object):
        def f(self): print "f()"

    class Mixin3(Mixin3Base): pass

    class Foo(object):
        __metaclass__ = add_mixins(Mixin1, Mixin2, Mixin3)

        def a(self): print "a()"

    class Bar(Foo):
        def f(self): print "Bar.f()"

    def test_class(cls):
        print "Testing {0}".format(cls.__name__)
        f = cls()
        f.a()
        f.b()
        f.c()
        f.d()
        f.e()
        f.f()
        print (issubclass(cls, Mixin1) or 
               issubclass(cls, Mixin2) or
               issubclass(cls, Mixin3))

    test_class(Foo)
    test_class(Bar)
4

你可以把这些方法添加成函数:

Foo.b = Mixin.b.im_func
Foo.c = Mixin.c.im_func
9
def mixer(*args):
    """Decorator for mixing mixins"""
    def inner(cls):
        for a,k in ((a,k) for a in args for k,v in vars(a).items() if callable(v)):
            setattr(cls, k, getattr(a, k).im_func)
        return cls
    return inner

class Mixin(object):
    def b(self): print "b()"
    def c(self): print "c()"

class Mixin2(object):
    def d(self): print "d()"
    def e(self): print "e()"


@mixer(Mixin, Mixin2)
class Foo(object):
    # Somehow mix in the behavior of the Mixin class,
    # so that all of the methods below will run and
    # the issubclass() test will be False.

    def a(self): print "a()"

f = Foo()
f.a()
f.b()
f.c()
f.d()
f.e()
print issubclass(Foo, Mixin)

输出结果:

a()
b()
c()
d()
e()
False

撰写回答