什么是 mixin,它有什么用处?

1333
在《Python编程》一书中,马克·卢茨提到了术语mixin。我来自C/C++/C#背景,之前从未听说过这个术语。什么是mixin?
这个例子(我链接它是因为它相当长)中看来,我认为它是使用多重继承来扩展类而不是正常的子类化。这样理解对吗?
为什么我要这样做而不是将新功能放入子类中?同样,为什么mixin /多重继承方法比使用组合更好?
mixin和多重继承有什么区别?这只是一个语义问题吗?

2
对于C++(在Windows上),Active Template Library利用CRTP(“Curiously Recurring Template Pattern”)将多个基类功能添加到您的类中。这类似于混合提供的功能。调试过程非常有趣。 - JBRWilkinson
Mixin在面向对象编程中是一种反模式(在我个人看来):特质和Mixin不是面向对象编程 - yegor256
18个回答

967

混入是一种特殊的多重继承方式,主要有两种情况需要使用混入:

  1. 为一个类提供许多可选功能。
  2. 在许多不同的类中使用一个特定的功能。

例如第一种情况可以考虑 werkzeug 的请求和响应系统。 我可以通过以下方式创建一个普通的请求对象:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

如果我想添加接受头支持,我会这样做

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

如果我想创建一个支持接受头、Etags、身份认证和用户代理支持的请求对象,我可以这样做:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

class Request(AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin, BaseRequest):
    pass

这两种方式的区别微妙,但在上面的例子中,mixin类并不是为了独立存在而设计的。在更传统的多重继承中,AuthenticationMixin(例如)可能更像Authenticator。也就是说,该类可能被设计成可以独立存在。


195
第三种情况是:您希望为一个类提供许多(非可选)功能,但您希望将这些功能放在不同的类(和不同的模块)中,以便每个模块都涉及一项功能(行为)。换句话说,并非为了重复使用,而是为了分隔功能。 - bootchk
79
在这个示例中可能不是问题,但通常你会把主要的基类放在括号的最后一个元素,以此来创建继承链:Request==>Mixin==>...==>BaseRequest。参考这里:http://www.ianlewis.org/en/mixins-and-python - hillel
16
@hillel 很好的观点,但请记住,Python 会按从左到右的顺序调用超类的方法(例如当您需要覆盖构造函数时)。 - Eliseu Monar dos Santos
这并没有真正解释什么是 mixin,以及它与普通类的区别,普通类也可以多重继承。 - Osman-pasha
5
从混合类继承并没有什么特别的地方。它只是一个旨在与至少一个其他基类一起使用的类。 - chepner

296

首先,你应该注意到mixin只存在于支持多继承的语言中。你不能在Java或C#中使用mixin。

基本上,mixin是一个独立的基础类型,为子类提供有限的功能和多态谐振。如果你想用C#来思考,可以把它看作一个接口,因为它已经被实现了,所以你只需继承它并从它的功能中受益。

Mixin通常范围狭窄,不意味着要扩展。

[编辑-关于为什么:]

既然你问了,我觉得我应该解释一下为什么。最大的好处是你不必反复自己写同样的代码。在C#中,mixin可能最有用的地方可能是清理模式。每当你实现IDisposable时,你几乎总是需要遵循相同的模式,但你最终会编写和重写具有微小差异的相同基本代码。如果有可扩展的Disposal mixin,你可以节省很多额外的打字。

[编辑2 - 回答你的其他问题]

一个mixin和多重继承的区别是什么?只是语义上的区别吗?

是的。mixin和标准多重继承之间的区别只是语义上的;具有多重继承的类可能会在其多重继承中使用mixin。

mixin的目的是创建一个类型,可以通过继承“混入”到任何其他类型中,而不影响继承类型,同时为该类型提供一些有益的功能。

再次思考已经实现的接口。

我个人不使用mixin,因为我主要在不支持它们的语言中进行开发,所以我很难想出一个合适的例子来让你豁然开朗。但我会再试试。我将使用一个虚构的例子--大多数语言已经以某种方式提供了这个特性--但希望能够解释如何创建和使用mixin。试试看:

假设你有一个需要进行XML序列化和反序列化的类型。你想要这个类型提供一个"ToXML"方法,返回一个包含该类型数据值的XML片段字符串,以及一个"FromXML"方法,允许该类型从XML片段字符串中重构其数据值。这只是一个假设的例子,因此可能使用文件流,或来自语言运行时库的XML Writer类...无论如何,关键是你想将对象序列化为XML,并从XML获得一个新对象。
在这个例子中,另一个重要的点是你想以一种通用的方式来实现这一点。你不想为每个需要序列化的类型都实现"ToXML"和"FromXML"方法,你希望有一些通用的方法来确保你的类型可以这样做,而且可以正常工作。你想要代码的重用。
如果你的编程语言支持,你可以创建XmlSerializable Mixin来完成你的工作。这个类型将实现ToXML和FromXML方法。它将能够使用某种机制(不重要)从与之混合的任何类型中收集所有必要的数据,以构建ToXML返回的XML片段,同样能够在调用FromXML时恢复数据。
那就是全部。要使用它,你需要让任何需要序列化到XML的类型继承XmlSerializable。每当你需要序列化或反序列化该类型时,只需调用ToXML或FromXML。实际上,由于XmlSerializable是一个完整的类型并且支持多态性,你可以构建一个文档序列化器,它不知道你原始类型的任何信息,只接受一个XmlSerializable类型的数组,这是可行的。
现在想象一下,将这个场景用于其他事情,比如创建一个Mixin,确保每个混合它的类都记录每个方法调用,或者提供了事务功能的Mixin。这个列表可以继续下去。
如果你只是把mixin看作是一个小型基础类型,旨在为类型添加少量功能而不影响该类型本身,那么你就成功了。
希望能对你有所帮助。 :)

27
嘿,你喜欢那个词组“多态共振”吗?是我自己想出来的。也许是在物理学某处听到的,但我不确定。 - Randolpho
55
我对你的第一句话有些不同意。Ruby 是一种单继承语言,混入(mixin)是向给定类添加方法的方式,而不需要从另一个类继承。 - Keltia
26
我认为mixin在定义上就是多重继承。在Ruby中,它们是一个monkeypatch(或其他东西),而不是真正的mixin。虽然Ruby的开发者可能称之为mixin,但它是一种不同的东西。 - S.Lott
12
实际上,一个真正的混合类不能使用多重继承。混合类可以在不继承它的情况下将另一个类的方法、属性等包含到自己里面。这通常会带来与多态性相似的代码重用优势,但避免了确定父类关系(例如菱形继承问题)所带来的问题。支持混合类的语言也倾向于允许部分包含混合类的内容(听起来有点像方面编程)。 - Trevor
15
记录一下,Java现在支持带有默认方法的mixin。 - shmosel
显示剩余15条评论

264
这篇回答旨在用示例来解释 mixin,这些示例具有以下特点:
- 自包含:短小精悍,不需要了解任何库即可理解示例。 - 使用 Python,而非其他语言。 - 考虑到该帖子是关于 Python 的,因此理解存在其他语言的示例(如 Ruby)也是可以的。
此外,它还应该考虑到一个有争议的问题:

多重继承是否必要来定义 mixin?

定义 我还没有看到 "权威" 来源的引用清楚地说明 Python 中的 mixin 是什么。
我看到了两种可能的 mixin 定义(如果它们被认为与其他类似的概念(如抽象基类)不同),人们并不完全同意哪一种是正确的。
共识可能会因不同的语言而异。 定义 1:无多重继承 mixin 是这样一种类,即类的某些方法使用了未在该类中定义的方法。
因此,该类并不意味着实例化,而是作为基类。否则,实例将具有无法调用的方法,会引发异常。
一些来源添加的约束条件是,该类不能包含数据,只能包含方法,但我不明白这为什么是必要的。然而,在实践中,许多有用的混合类没有任何数据,并且没有数据的基类更容易使用。
一个经典的例子是仅从<===实现所有比较运算符:
class ComparableMixin(object):
    """This class has methods which use `<=` and `==`,
    but this class does NOT implement those methods."""
    def __ne__(self, other):
        return not (self == other)
    def __lt__(self, other):
        return self <= other and (self != other)
    def __gt__(self, other):
        return not self <= other
    def __ge__(self, other):
        return self == other or self > other

class Integer(ComparableMixin):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) <  Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) >  Integer(0)
assert Integer(1) >= Integer(1)

# It is possible to instantiate a mixin:
o = ComparableMixin()
# but one of its methods raise an exception:
#o != o 

这个例子本可以通过使用functools.total_ordering()装饰器实现,但是我们的目的是重新发明轮子。
import functools

@functools.total_ordering
class Integer(object):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)

定义2:多重继承

混合(mixin)是一种设计模式,其中基类的某些方法使用了它没有定义的方法,并且该方法应由另一个基类实现,而不是像定义1中那样由派生类实现。

术语 mixin 类 指的是旨在在该设计模式中使用的基类(TODO 是那些使用该方法的类还是那些实现它的类?)

很难确定给定类是否是混合类:该方法可能仅在派生类上实现,在这种情况下,我们回到了定义1。 您必须考虑作者的意图。

此模式非常有趣,因为可以通过选择不同的基类重新组合功能:

class HasMethod1(object):
    def method(self):
        return 1

class HasMethod2(object):
    def method(self):
        return 2

class UsesMethod10(object):
    def usesMethod(self):
        return self.method() + 10

class UsesMethod20(object):
    def usesMethod(self):
        return self.method() + 20

class C1_10(HasMethod1, UsesMethod10): pass
class C1_20(HasMethod1, UsesMethod20): pass
class C2_10(HasMethod2, UsesMethod10): pass
class C2_20(HasMethod2, UsesMethod20): pass

assert C1_10().usesMethod() == 11
assert C1_20().usesMethod() == 21
assert C2_10().usesMethod() == 12
assert C2_20().usesMethod() == 22

# Nothing prevents implementing the method
# on the base class like in Definition 1:

class C3_10(UsesMethod10):
    def method(self):
        return 3

assert C3_10().usesMethod() == 13

权威的 Python 出现

集合.abc 的官方文档 中,文件明确使用术语 Mixin 方法

如果一个类:

  • 实现了 __next__
  • 从单个类 Iterator 继承

那么该类将免费获得一个 __iter__ mixin 方法

因此,至少在这一点上,mixin 不需要多重继承,并且与定义 1 一致。

当然,文档在不同的地方可能会相互矛盾,并且其他重要的 Python 库可能在它们的文档中使用其他定义。

本页面还使用术语 Set mixin,这清楚地表明类如 SetIterator 可以被称为 Mixin 类。

在其他语言中

  • Ruby:如主要参考书籍Programming Ruby和《Ruby编程语言》所述,明显不需要多重继承进行混合。

  • C++:设置=0virtual方法是纯虚方法。

    定义1与抽象类的定义相同(具有纯虚方法的类),该类无法实例化。

    使用虚拟继承可能会出现定义2:Multiple Inheritance from two derived classes


这个页面还使用了“Set mixin”这个术语,这清楚地表明像SetIterator这样的类可以被称为Mixin类。那么它是否与您的Mixin类定义1相矛盾,该定义要求Mixin类使用它们未定义的方法,因为Iterator违反了该要求(参见其实现)? - Géry Ogam

49

我认为它们是使用多重继承的一种有纪律的方式 - 因为 mixin 最终只是另一个 Python 类,遵循了所谓的混合类约定。

我的理解是,所谓 Mixin 的约定如下:

  • 添加方法但不添加实例变量(类常量可以)
  • 仅从 object(在 Python 中)继承

这样它就限制了多重继承的潜在复杂性,并通过限制查看范围来使程序流程跟踪相对容易(与完整的多重继承相比)。 它们类似于Ruby 模块

如果我想要添加实例变量(比单继承允许的更灵活),那么我倾向于使用组合。

话虽如此,我确实见过被称为 XYZMixin 的类具有实例变量的情况。


43

我认为之前的回答已经很好地定义了什么是MixIns,但是为了更好地理解它们,与从代码/实现角度比较MixInsAbstract Classes以及Interfaces可能会有所帮助:

1. 抽象类

  • 需要包含一个或多个抽象方法的

  • 抽象类可以包含状态(实例变量)和非抽象方法

2. 接口

  • 接口仅包含抽象方法(没有非抽象方法和内部状态)

3. MixIns

  • MixIns(像接口一样)不包含内部状态(实例变量)

  • MixIns包含一个或多个非抽象方法(与接口不同,它们可以包含非抽象方法)

在Python等语言中,这只是约定,因为以上所有内容都被定义为class。然而,抽象类、接口MixIns的共同特点是它们不应单独存在,即不应被实例化。


1
谢谢!那个比较让人更清楚了! - Gwang-Jin Kim

42
一个Mixin与多重继承有什么区别?这只是一种语义上的问题吗?
Mixin是多重继承的一种有限形式。在某些语言中,将Mixin添加到类的机制(从语法上讲)与继承略有不同。
特别是在Python的情况下,Mixin是一个父类,为子类提供功能,但不打算自己实例化。
如果可能会混淆为Mixin的类实际上可以被实例化和使用,那么可能会导致你说“那只是多重继承,而不是真正的Mixin”,因此这是一个语义上的,并且非常真实的差异。
多重继承的示例:OrderedCounter。来自文档
class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

它从collections模块继承了CounterOrderedDict两个类。

CounterOrderedDict都是可以单独实例化和使用的。然而,通过同时继承它们,我们可以得到一个有序的计数器,并且可以重复使用每个对象中的代码。

这是一种强大的代码重用方式,但也可能会有问题。如果发现其中一个对象存在漏洞,在不小心修复的情况下可能会在子类中产生错误。

混入的示例

混入通常被视为在没有潜在耦合问题的情况下获得代码重用的方法,例如OrderedCounter中的协作多重继承。当您使用混入时,您使用的功能与数据之间的耦合程度不那么紧密。

与上面的示例不同,混入不打算单独使用。它提供新的或不同的功能。

例如,标准库中有一些socketserver库中的混入

Forking and threading versions of each type of server can be created using these mix-in classes. For instance, ThreadingUDPServer is created as follows:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

The mix-in class comes first, since it overrides a method defined in UDPServer. Setting the various attributes also changes the behavior of the underlying server mechanism.

在这种情况下,mixin方法覆盖了UDPServer对象定义中的方法,以实现并发。
被覆盖的方法似乎是process_request,它还提供了另一个方法process_request_thread。以下是来自源代码的代码:
class ThreadingMixIn:
        """Mix-in class to handle each request in a new thread."""

        # Decides how threads will act upon termination of the
        # main process
        daemon_threads = False

        def process_request_thread(self, request, client_address):
            """Same as in BaseServer but as a thread.
            In addition, exception handling is done here.
            """
            try:
                self.finish_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
            finally:
                self.shutdown_request(request)

        def process_request(self, request, client_address):
            """Start a new thread to process the request."""
            t = threading.Thread(target = self.process_request_thread,
                                 args = (request, client_address))
            t.daemon = self.daemon_threads
            t.start()

一个人为的例子

这是一个主要用于演示目的的混合项 - 大多数对象将超出此 repr 的有用性:

class SimpleInitReprMixin(object):
    """mixin, don't instantiate - useful for classes instantiable
    by keyword arguments to their __init__ method.
    """
    __slots__ = () # allow subclasses to use __slots__ to prevent __dict__
    def __repr__(self):
        kwarg_strings = []
        d = getattr(self, '__dict__', None)
        if d is not None:
            for k, v in d.items():
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        slots = getattr(self, '__slots__', None)
        if slots is not None:
            for k in slots:
                v = getattr(self, k, None)
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        return '{name}({kwargs})'.format(
          name=type(self).__name__,
          kwargs=', '.join(kwarg_strings)
          )

使用方法如下:

class Foo(SimpleInitReprMixin): # add other mixins and/or extend another class here
    __slots__ = 'foo',
    def __init__(self, foo=None):
        self.foo = foo
        super(Foo, self).__init__()

和用法:

>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)

34

混入(Mixins)是编程中的一个概念,类提供了功能,但不适合用于实例化。混入的主要目的是提供独立的功能,最好在混入本身与其他混入没有继承关系并避免状态。在像Ruby这样的语言中,有一些直接支持的语言,但是在Python中没有。但是,可以使用多类继承来执行Python中提供的功能。

我观看了这个视频http://www.youtube.com/watch?v=v_uKI2NOLEM,以了解混入的基础知识。对于初学者来说,它非常有用,可以了解混入的基础知识、工作原理以及在实现混入时可能面临的问题。

维基百科仍然是最好的:http://en.wikipedia.org/wiki/Mixin


13

我认为这里已经有一些很好的解释了,但我想提供另一种观点。

在Scala中,可以像这里所述那样进行mixin,但非常有趣的是,这些mixin实际上被“融合”在一起,以创建一种新的类继承。实质上,您不会从多个类/ mixin继承,而是生成一种具有所有mixin属性以继承的新类型的类。这是有道理的,因为Scala基于JVM,在Java 8时还不支持多重继承。顺便说一下,这种mixin类类型是Scala中称为特征(Trait)的一种特殊类型。

这是通过定义类的方式进行暗示的: class NewClass extends FirstMixin with SecondMixin with ThirdMixin ...

我不确定CPython解释器是否执行相同的操作(mixin类组合),但我并不感到惊讶。此外,来自C++背景的我不会将ABC或“接口”等同于mixin-它们是类似的概念,但在用途和实现上有所不同。


13
这个概念来自于Steve's Ice Cream,是由Steve Herrell在1973年在马萨诸塞州萨默维尔创办的一家冰淇淋店,当时他们会将糖果、蛋糕等材料加入到基础的冰淇淋味道中(如香草味、巧克力味等)。受Steve's Ice Cream的启发,Flavors编程语言的设计人员在1979年首次将这一概念引入了编程语言中,其中mix-ins是用于增强其他类的小型辅助类,而flavors则是大型的独立类。
所以这个概念的要点是一个可重用的扩展(“可重用”与“独占”相对;“扩展”与“基础”相对)。混合类可以是抽象类或具体类,因为混合类具有不完整的接口,而抽象类具有不完整的实现,具体类具有完整的实现。混合类可以在单继承中被继承,通常用于扩展子类的接口,也可以在多继承中被继承,通常用于扩展超类的实现。

混入类名通常以“-MixIn”、“-able”或“-ible”结尾,以强调它们的性质,就像Python标准库中socketserver模块的ThreadingMixInForkingMixIn类以及collections.abc模块的HashableIterableCallableAwaitableAsyncIterableReversible类一样。

这里是一个抽象混入内置类Sized的示例,用于单继承中通过扩展QueueStack的接口,提供__len__特殊方法:

import abc
import collections.abc


class Queue(collections.abc.Sized, metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def enqueue(self, item):
        raise NotImplementedError

    @abc.abstractmethod
    def dequeue(self):
        raise NotImplementedError


class Stack(collections.abc.Sized, metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def push(self, item):
        raise NotImplementedError

    @abc.abstractmethod
    def pop(self):
        raise NotImplementedError

以下是一个具体的混合类示例LoggingMixIn,用于多重继承,通过扩展内置superlistdict的实现以具有日志记录功能。
import logging


class LoggingMixIn:

    def __setitem__(self, key, value):
        logging.info('Setting %r to %r', key, value)
        super().__setitem__(key, value)

    def __delitem__(self, key):
        logging.info('Deleting %r', key)
        super().__delitem__(key)


class LoggingList(LoggingMixIn, list):
    pass


class LoggingDict(LoggingMixIn, dict):
    pass

>>> logging.basicConfig(level=logging.INFO)
>>> l = LoggingList([False])
>>> d = LoggingDict({'a': False})
>>> l[0] = True
INFO:root:Setting 0 to True
>>> d['a'] = True
INFO:root:Setting 'a' to True
>>> del l[0]
INFO:root:Deleting 0
>>> del d['a']
INFO:root:Deleting 'a'

10

或许一些例子可以帮助理解。

如果你正在构建一个类,想让它像一个字典一样工作,你可以定义所有必要的 __ __ 方法。但这有点麻烦。作为替代方案,你可以只定义一部分方法,并从 UserDict.DictMixin(在py3k中移动到collections.DictMixin)继承(除了其他继承)。这将自动定义出其余的字典API。

第二个例子:GUI工具包wxPython允许你创建具有多列的列表控件(例如Windows资源管理器中的文件显示)。默认情况下,这些列表比较基础。你可以通过从ListCtrl继承并添加适当的mixins来增加额外的功能,例如按列标题点击对列表进行排序的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接