什么是Mixin,它为什么有用?

Python编程中,Mark Lutz提到了术语混合。我来自C/C++ /C#背景,以前没有听说过这个术语。什么是混合?

这个例子(我链接到它是因为它很长)的行之间阅读,我假设这是使用多重继承来扩展类而不是正确的子类化的情况。这是正确的吗?

为什么我要这样做而不是将新功能放入子类中?就此而言,为什么混合/多重继承方法比使用组合更好?

混合与多重继承的区别是什么?这只是语义学的问题吗?

445779 次浏览

首先,您应该注意,混合只存在于多继承语言中。您不能在Java或C#中进行混合。

基本上,Mixin是一种独立的基类型,为子类提供有限的功能和多态共振。如果你在考虑使用C#,请考虑一个你不必实际实现的接口,因为它已经实现了;你只需从它继承并从它的功能中受益。

混合物的范围通常很窄,并不意味着要扩展。

[编辑-至于为什么:]

既然你问了,我想我应该解释一下原因。最大的好处是你不必自己一遍又一遍地做。在C#中,混入可以受益的最大地方可能是处置模式。每当你实现IDisposable时,你几乎总是想遵循同样的模式,但你最终会编写和重写相同的基本代码,只是有一些小的变化。如果有一个可扩展的Disposal混入,你可以为自己节省大量额外的输入。

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

混合与多重继承的区别是什么?这只是语义学的问题吗?

是的。混合和标准多重继承之间的区别只是语义学问题;具有多重继承的类可能会使用混合作为该多重继承的一部分。

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

再一次,考虑一个已经实现的接口。

我个人不使用Mixins,因为我主要用一种不支持它们的语言开发,所以我很难想出一个像样的例子来为你提供“啊哈!”的时刻。但我会再试一次。我将使用一个人为的例子——大多数语言已经以某种方式提供了该功能——但希望这将解释应该如何创建和使用Mixins。如下:

假设您有一个类型,您希望能够序列化到XML和从XML。您希望该类型提供一个“ToXML”方法,该方法返回一个字符串,该字符串包含一个带有该类型数据值的XML片段,以及一个“FromXML”,该方法允许该类型从字符串中的XML片段重建其数据值。同样,这是一个人为的示例,因此您可能使用文件流,或者语言运行时库中的XML Writer类……等等。关键是您想将对象序列化为XML并从XML获取一个新对象。

这个例子中的另一个要点是,您希望以通用的方式执行此操作。您不希望必须为要序列化的每个类型实现“ToXML”和“FromXML”方法,您需要一些通用的方法来确保您的类型会执行此操作并且它只是有效。您希望代码重用。

如果您的语言支持它,您可以创建XmlSerializable混合来为您完成您的工作。此类型将实现ToXML和FromXML方法。它将使用一些对示例不重要的机制,能够从与之混合的任何类型收集所有必要的数据以构建ToXML返回的XML片段,并且在调用FromXML时同样能够恢复该数据。

就是这样。要使用它,你需要从XmlSerializable继承任何需要序列化为XML的类型。每当你需要序列化或反序列化该类型时,你只需调用ToXML或FromXML。事实上,由于XmlSerializable是一种成熟的类型和多态,你可以想象构建一个对你的原始类型一无所知的文档序列化器,只接受例如XmlSerializable类型的数组。

现在想象一下将这个场景用于其他事情,比如创建一个混合,确保每个混合它的类都记录每个方法调用,或者一个混合,为混合它的类型提供事务性。

如果你只是认为一个混合是一个小的基本类型,旨在为一个类型添加少量的功能,而不会影响该类型,那么你是金。

希望如此:)

也许Ruby的一个例子可以提供帮助:

您可以包含MixinComparable并定义一个函数"<=>(other)",Mixin提供所有这些函数:

<(other)>(other)==(other)<=(other)>=(other)between?(other)

它通过调用<=>(other)并返回正确的结果来做到这一点。

如果两个对象相等,"instance <=> other"返回0;如果instance大于other,则返回小于0;如果other大于,则返回大于0。

我建议不要在新的Python代码中混入,如果你能找到任何其他方法来解决它(例如组合而不是继承,或者只是将方法修补到你自己的类中),这并不是更多的努力。

在旧式类中,你可以使用混合来从另一个类中获取一些方法。但在新式世界中,一切,甚至混合,都继承自object。这意味着任何多重继承的使用都自然引入了MRO问题

在Python中,有很多方法可以使多重继承MRO工作,最值得注意的是超级()函数,但这意味着你必须使用超级()来完成整个类层次结构,并且理解控制流要困难得多。

也许几个例子会有所帮助。

如果你正在构建一个类并且希望它像字典一样运行,你可以定义所有必要的__ __方法。但这有点麻烦。作为替代方案,你可以只定义几个,并从UserDict.DictMixin继承(除了任何其他继承之外)(在py3k中移动到collections.DictMixin)。这将具有自动定义字典api所有其余部分的效果。

第二个例子:GUI工具包wxPython允许您制作具有多列的列表控件(例如,Windows资源管理器中的文件显示)。默认情况下,这些列表相当基本。您可以添加附加功能,例如通过单击列标题、从ListCtrl继承并添加适当的混合来按特定列对列表进行排序的能力。

混合是一种特殊的多重继承。使用混合的主要有两种情况:

  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

区别很微妙,但在上面的例子中,混合类并没有独立存在。在更传统的多重继承中,AuthenticationMixin(例如)可能更像Authenticator。也就是说,类可能会被设计成独立存在。

这不是一个Python示例,但在D编程语言中,术语#0用于指代以几乎相同的方式使用的构造;向类添加一堆东西。

在D(顺便说一句,它不做MI)中,这是通过将模板(想想语法感知和安全宏,你就会接近)插入范围来完成的。这允许类、结构、函数、模块或任何东西中的一行代码扩展为任意数量的声明。

我听说你有c#背景。所以一个好的起点可能是. NET的混合实现。

您可能想在http://remix.codeplex.com/处查看co的exex项目

观看lang.net研讨会链接以获得概览。还有更多内容请留档在codidex页面上。

问候斯特凡

Mixins是编程中的一个概念,其中类提供功能,但并不意味着用于实例化。Mixins的主要目的是提供独立的功能,最好是Mixins本身与其他Mixins没有继承并且也避免状态。在Ruby等语言中,有一些直接的语言支持,但对于Python没有。但是,你可以使用多类继承来执行Python提供的功能。

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

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

Mixin提供了一种在类中添加功能的方法,即你可以通过将模块包含在所需类中来与模块中定义的方法进行交互。虽然ruby不支持多重继承,但提供了Mixin作为实现这一目标的替代方案。

下面是一个示例,解释了如何使用Mixin实现多重继承。

module A    # you create a moduledef a1  # lets have a method 'a1' in itenddef a2  # Another method 'a2'endend
module B    # let's say we have another moduledef b1  # A method 'b1'enddef b2  #another method b2endend
class Sample    # we create a class 'Sample'include A   # including module 'A' in the class 'Sample' (mixin)include B   # including module B as well
def S1      #class 'Sample' contains a method 's1'endend
samp = Sample.new    # creating an instance object 'samp'
# we can access methods from module A and B in our class(power of mixin)
samp.a1     # accessing method 'a1' from module Asamp.a2     # accessing method 'a2' from module Asamp.b1     # accessing method 'b1' from module Bsamp.b2     # accessing method 'a2' from module Bsamp.s1     # accessing method 's1' inside the class Sample

我刚刚使用python混合来实现python milter的单元测试。通常,milter与MTA对话,使得单元测试变得困难。测试混合覆盖与MTA对话的方法,并创建一个由测试用例驱动的模拟环境。

因此,您需要一个未修改的milter应用程序,例如spfmilter,并混合TestBase,如下所示:

class TestMilter(TestBase,spfmilter.spfMilter):def __init__(self):TestBase.__init__(self)spfmilter.config = spfmilter.Config()spfmilter.config.access_file = 'test/access.db'spfmilter.spfMilter.__init__(self)

然后,在milter应用程序的测试用例中使用TestMilter:

def testPass(self):milter = TestMilter()rc = milter.connect('mail.example.com',ip='192.0.2.1')self.assertEqual(rc,Milter.CONTINUE)rc = milter.feedMsg('test1',sender='good@example.com')self.assertEqual(rc,Milter.CONTINUE)milter.close()

http://pymilter.cvs.sourceforge.net/viewvc/pymilter/pymilter/Milter/test.py?revision=1.6&; view=标记

我认为它们是使用多重继承的一种有纪律的方式——因为最终混合只是另一个python类,它(可能)遵循关于称为混合的类的约定。

我对管理你称之为混合的东西的惯例的理解是混合:

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

通过这种方式,它限制了多重继承的潜在复杂性,并通过限制必须查看的位置(与完全多重继承相比),使跟踪程序的流程变得相当容易。

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

话虽如此,我已经看到名为XYZMisin的类确实有实例变量。

这个答案旨在解释Mixin有例子,它们是:

  • 自给自足:简短,无需了解任何库即可理解示例。

  • 在python,而不是其他语言。

    可以理解的是,有来自其他语言的示例,例如Ruby,因为该术语在这些语言中更常见,但这是一个python线程。

委员会还应审议有争议的问题:

多重继承是必要的还是不必要的?

定义

我还没有看到来自“权威”来源的引用清楚地说明什么是Python中的混合。

我已经看到了混合的两种可能的定义(如果它们被认为不同于其他类似的概念,如抽象基类),人们并不完全同意哪一个是正确的。

不同语言之间的共识可能会有所不同。

定义1:没有多重继承

混合是一个类,使得该类的某些方法使用该类中未定义的方法。

因此,类不是要实例化的,而是作为基类。否则,实例将具有在不引发异常的情况下无法调用的方法。

一些源添加的一个约束是类可能不包含数据,只包含方法,但我不明白为什么这是必要的。然而,在实践中,许多有用的混合没有任何数据,没有数据的基类更容易使用。

一个经典的例子是仅从<===实现所有比较运算符:

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 <= otherdef __ge__(self, other):return self == other or self > other
class Integer(ComparableMixin):def __init__(self, i):self.i = idef __le__(self, other):return self.i <= other.idef __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_orderingclass Integer(object):def __init__(self, i):self.i = idef __le__(self, other):return self.i <= other.idef __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:多重继承

混合是一种设计模式,其中基类的某些方法使用它未定义的方法,并且该方法旨在由另一个基类实现,而不是像定义1中的派生。

术语混合类指的是旨在在该设计模式中使用的基类(待办事项是使用该方法的基类,还是实现该方法的基类?)

判断一个给定的类是否是一个混合并不容易:该方法可以在派生类上实现,在这种情况下,我们回到定义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): passclass C1_20(HasMethod1, UsesMethod20): passclass C2_10(HasMethod2, UsesMethod10): passclass C2_20(HasMethod2, UsesMethod20): pass
assert C1_10().usesMethod() == 11assert C1_20().usesMethod() == 21assert C2_10().usesMethod() == 12assert 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事件

collections.abc官方文件处,留档明确使用术语混合方法

它指出,如果一个类:

  • 实现__next__
  • 继承自单个类Iterator

然后类免费获得__iter__混合法

因此,至少在留档的这一点上,混合不需要多重继承与定义1是一致的。

当然,留档在不同的点上可能是矛盾的,其他重要的Python库可能在其留档中使用其他定义。

本页还使用了术语Set mixin,它清楚地表明像SetIterator这样的类可以称为混合类。

在其他语言

  • Ruby:显然不需要混合的多重继承,如Ruby编程和Ruby编程语言等主要参考书中提到的

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

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

    定义2可以使用虚拟继承:从两个派生类的多重继承

我认为这里有一些很好的解释,但我想提供另一个视角。

在Scala中,你可以像这里描述的那样做混合,但非常有趣的是,混合实际上被“融合”在一起以创建一种新的类来继承。本质上,你不会从多个类/混合继承,而是生成一种新的类,其中包含混合继承的所有属性。这是有道理的,因为Scala基于目前不支持多重继承的JVM(截至Java8)。顺便说一句,这种混合类类型是一种特殊类型,在Scala中称为Trait。

它在类的定义方式中暗示:NewClass类扩展了FirstMisin的第二个和第三个混合…

我不确定CPython解释器是否也这样做(混合类组合),但我不会感到惊讶。此外,从C++的背景来看,我不会将ABC或“接口”称为混合-这是一个类似的概念,但在使用和实现上存在差异。

混合与多重继承的区别是什么?这只是语义学的问题吗?

混合是多重继承的一种有限形式。在某些语言中,向类添加混合的机制(在语法方面)与继承的机制略有不同。

特别是在Python的上下文中,混合是一个父类,它为子类提供功能,但不打算实例化自己。

可能导致你说“这只是多重继承,而不是真正的混合”的原因是,如果可能被混淆为混合的类实际上可以被实例化和使用——所以它确实是一个语义上的,非常真实的,差异。

多重继承的例子

这个例子,从留档,是一个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都旨在实例化并单独使用。但是,通过对它们进行子类化,我们可以拥有一个有序的计数器并重用每个对象中的代码。

这是一种重用代码的强大方法,但它也可能存在问题。如果发现其中一个对象中存在bug,不小心修复它可能会在子类中创建bug。

一个混合的例子

Mixins通常被提升为获得代码重用的方式,而不会出现合作多继承(如OrderedCounter)可能存在的潜在耦合问题。当你使用Mixins时,你使用的功能不会与数据紧密耦合。

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

例如,标准库有几个#0库中的Mixins

可以创建每种类型服务器的分叉和线程版本使用这些混合类。例如,ThreadingUDPServer是创建如下:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):pass

混合类排在第一位,因为它覆盖了定义在UDPServer。设置各种属性也会更改底层服务器机制。

在这种情况下,混合方法会覆盖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 processdaemon_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_threadst.start()

一个虚构的例子

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

class SimpleInitReprMixin(object):"""mixin, don't instantiate - useful for classes instantiableby 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 = foosuper(Foo, self).__init__()

及用法:

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

OP提到他/她在C++中从未听说过Mixin,也许是因为他们在C++中被称为奇怪的循环模板模式(CRTP)。此外,@Ciro Santilli提到Mixin是通过C++中的抽象基类实现的。虽然抽象基类可以用来实现Mixin,但这是一种矫枉过正,因为运行时虚拟函数的功能可以在编译时使用模板来实现,而不会在运行时查找虚拟表的开销。

CRTP模式详细描述这里

我已经使用下面的模板类将@Ciro Santilli的答案中的python示例转换为C++:

    #include <iostream>#include <assert.h>
template <class T>class ComparableMixin {public:bool operator !=(ComparableMixin &other) {return ~(*static_cast<T*>(this) == static_cast<T&>(other));}bool operator <(ComparableMixin &other) {return ((*(this) != other) && (*static_cast<T*>(this) <= static_cast<T&>(other)));}bool operator >(ComparableMixin &other) {return ~(*static_cast<T*>(this) <= static_cast<T&>(other));}bool operator >=(ComparableMixin &other) {return ((*static_cast<T*>(this) == static_cast<T&>(other)) || (*(this) > other));}protected:ComparableMixin() {}};
class Integer: public ComparableMixin<Integer> {public:Integer(int i) {this->i = i;}int i;bool operator <=(Integer &other) {return (this->i <= other.i);}bool operator ==(Integer &other) {return (this->i == other.i);}};
int main() {
Integer i(0) ;Integer j(1) ;//ComparableMixin<Integer> c; // this will cause compilation error because constructor is protected.assert (i < j );assert (i != j);assert (j >  i);assert (j >= i);
return 0;}

编辑:在比较混合中添加了受保护的构造函数,因此它只能被继承而不能实例化。更新了示例以显示当创建比较混合的对象时,受保护的构造函数将如何导致编译错误。

我认为以前的回答很好地定义了MixIns是什么。然而,为了更好地理解它们,从代码/实现的角度比较MixIns抽象类接口可能会很有用:

1.抽象类

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

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

2.接口

  • 接口包含抽象方法只有(没有非抽象方法,也没有内部状态)

3. MixIns

  • MixIns(如接口)不要包含内部状态(实例变量)
  • MixIns包含一个或多个非抽象方法(它们可以包含与接口不同的非抽象方法)

在例如Python中,这些只是约定,因为上述所有都被定义为classes。然而,抽象类,接口MixIns的共同特征是它们不应该独立存在,即不应该实例化。

这个概念来自史蒂夫的冰淇淋,这是一家由Steve Herrell于1973年在马萨诸塞州萨默维尔创立的冰淇淋店,其中混搭(糖果,蛋糕等)被混合到基本冰淇淋口味(香草,巧克力等)中。

受Steve的Ice Cream的启发,Lisp对象系统口味的设计者第一次将这个概念包含在编程语言中,其中混搭是为增强其他类而设计的小型助手类,口味是大型独立类。

因此,主要思想是混搭可重用扩展(“可重用”而不是“独占”;“扩展”而不是“基础”)。

与单继承或多继承、抽象类或具体类的概念正交。混合类可以用于单继承或多继承,可以是抽象类也可以是具体类。混合类接口不完整,抽象类实现不完整,具体类实现完整。

Mix-in类名通常后缀为'-MixIn','-able'或'-的',以强调它们的性质,就像在Python标准库中ForkingMixIn0模块的ThreadingMixInForkingMixIn类,以及ForkingMixIn1模块的HashableIterableCallableAwaitableAsyncIterableReversible类。

这是一个混合类的示例,用于扩展具有日志记录功能的Python内置listdict类:

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] = TrueINFO:root:Setting 0 to True>>> d['a'] = TrueINFO:root:Setting 'a' to True>>> del l[0]INFO:root:Deleting 0>>> del d['a']INFO:root:Deleting 'a'