python:什么是mixin,它们为什么有用?

在"编程Python"中,Mark Lutz提到了"mixin"。我来自C/ c++ / c#背景,以前从未听说过这个术语。什么是mixin?

从这个例子的字里话中(我已经链接到这个例子,因为它很长),我假设它是使用多重继承来扩展一个类,而不是使用"适当的"子类。这是正确的吗?

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

什么将mixin从多重继承中分离出来?这只是语义问题吗?


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

您希望为类提供许多可选特性。您希望在许多不同的类中使用一个特定的特性。

举第一个例子,考虑一下werkzeug的请求和响应系统。我可以创建一个普通的旧请求对象,方法是:

1
2
3
4
from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

如果我想添加accept header支持,我会这样做

1
2
3
4
from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

如果我想让一个请求对象支持accept header、etags、authentication和user agent支持,我可以这样做:

1
2
3
4
from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

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

区别是细微的,但是在上面的例子中,mixin类并不是单独创建的。在更传统的多重继承中,AuthenticationMixin(例如)可能更类似于Authenticator。也就是说,这个类可能被设计成独立存在的。


首先,您应该注意到mixin只存在于多继承语言中。您不能在Java或c#中执行mixin。

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

mixin的范围通常很窄,不打算进行扩展。

[编辑——至于原因:]

既然你问了,我想我应该说明原因。最大的好处是你不必一遍又一遍地自己做这件事。在c#中,mixin最大的优势可能来自于处理模式。每当您实现IDisposable时,您几乎总是希望遵循相同的模式,但是您最终编写并重新编写相同的基本代码,只需要稍加修改。如果有一个可扩展的处理混合,您可以为自己节省许多额外的输入。

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

What separates a mixin from multiple inheritance? Is it just a matter of semantics?

是的。mixin和标准多重继承之间的区别只是一个语义问题;具有多重继承的类可以使用mixin作为多重继承的一部分。

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

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

我个人不使用mixin,因为我主要是用一种不支持mixin的语言开发的,所以我很难想出一个像样的例子来为您提供那种"啊哈!"的时刻。但我会再试一次。我将使用一个精心设计的例子——大多数语言已经以某种方式或其他方式提供了该特性——但这将有望解释如何创建和使用mixin。是:

假设您有一个类型,希望能够序列化到XML和从XML序列化到XML。您希望该类型提供一个"ToXML"方法,该方法返回一个包含具有该类型数据值的XML片段的字符串,以及一个"FromXML"方法,该方法允许该类型从字符串中的XML片段重构其数据值。同样,这是一个人为设计的例子,所以可能您使用了文件流,或者语言运行时库中的XML Writer类……无论什么。关键是要将对象序列化为XML并从XML中获取一个新对象。

本例中另一个重要的观点是,您希望以一种通用的方式进行此操作。您不需要为希望序列化的每一种类型实现"ToXML"和"FromXML"方法,您需要一些通用的方法来确保您的类型能够这样做,并且它能够正常工作。您希望代码重用。

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

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

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

如果您只是将mixin看作一个小的基类型,设计它的目的是在不影响该类型的情况下向该类型添加少量功能,那么您就已经很出色了。

希望。:)


这个答案旨在用以下例子来解释mixin:

自包含:简短,不需要知道任何库就可以理解示例。

在Python中,而不是在其他语言中。

可以理解其他语言(比如Ruby)也有这样的例子,因为这个术语在这些语言中更为常见,但这是一个Python线程。

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

Is multiple inheritance necessary or not to characterize a mixin?

定义

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

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

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

定义1:没有多重继承

mixin是这样一个类,该类的某些方法使用了类中没有定义的方法。

因此,该类不打算被实例化,而是作为一个基类。否则,实例将拥有无法在不引发异常的情况下调用的方法。

一些源添加的一个约束是类可能不包含数据,只包含方法,但我不认为这是必要的。然而在实践中,许多有用的mixin没有任何数据,没有数据的基类使用起来更简单。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()装饰器实现,但是这里的游戏是重新发明轮子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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类指的是打算在该设计模式中使用的基类(使用该方法的基类,还是实现该方法的基类?)

要确定给定的类是否是mixin并不容易:方法可以只在派生类上实现,在这种情况下,我们回到定义1。你必须考虑作者的意图。

这个模式很有趣,因为可以用基类的不同选择重新组合功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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出现

在官方文件中收藏。文档显式地使用术语Mixin方法。

它指出,如果一个类:

实现了__next__继承自一个类Iterator

然后该类免费获得一个__iter__ mixin方法。

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

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

这个页面还使用了术语Set mixin,它清楚地表明,像SetIterator这样的类可以被称为Mixin类。

在其他语言

Ruby:很明显,mixin不需要多重继承,如编程Ruby和Ruby编程语言等主要参考书中所述

c++:未实现的方法是纯虚方法。

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

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


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

我对规范所谓Mixin的约定的理解是:

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

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

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

尽管如此,我还是看到了一些名为XYZMixin的类,它们确实有实例变量。


mixin是编程中的一个概念,在这个概念中,类提供功能,但不打算用于实例化。mixin的主要目的是提供独立的功能,最好是mixin本身不与其他mixin具有继承性,并且避免状态。在像Ruby这样的语言中,有一些直接的语言支持,但是对于Python则没有。但是,您可以使用多类继承来执行Python中提供的功能。

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

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


What separates a mixin from multiple inheritance? Is it just a matter of semantics?

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

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

可能会让您说"这只是多重继承,而不是真正的mixin"的原因是,如果可能混淆为mixin的类实际上可以实例化和使用—所以它确实是一个语义上的、非常真实的区别。

多重继承的例子

这个例子来自文档,是一个OrderedCounter:

1
2
3
4
5
6
7
8
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。

混合蛋白的例子

mixin通常被推广为一种获得代码重用的方法,而不存在协作多继承(如OrderedCounter)可能存在的潜在耦合问题。当您使用mixin时,您使用的功能并不是与数据紧密耦合的。

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

例如,标准库在socketserver库中有两个mixin。

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

1
2
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。下面是源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()

的例子

这是一个混合,主要是为了演示的目的-大多数对象的发展将超出这个报告的用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)
          )

使用方法是:

1
2
3
4
5
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__()

和用法:

1
2
3
4
5
6
>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)


我建议不要在新的Python代码中混用,如果您能找到其他方法(例如组合而不是继承,或者只是在您自己的类中使用monkey-patch方法),这样做不会花费太多精力。

在旧式类中,可以使用mix- In从另一个类中获取一些方法。但是在新风格的世界里,所有的东西,甚至是混合的东西,都继承自object。这意味着任何对多重继承的使用都会自然地引入MRO问题。

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


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

在Scala中,您可以像这里描述的那样执行mixin,但是非常有趣的是,mixin实际上是"融合"在一起的,以创建一种新的类来继承。本质上,您不会从多个类/mixin继承,而是生成一种新的类,该类具有mixin要继承的所有属性。这是有道理的,因为Scala是基于JVM的,而JVM目前不支持多重继承(从Java 8开始)。

它暗示了一个类的定义方式:类NewClass用SecondMixin和ThirdMixin扩展FirstMixin…

我不确定CPython解释器是否执行相同的操作(mixin类组合),但我不会感到惊讶。此外,由于有c++背景,我不会将ABC或相当于mixin的"接口"称为ABC或"接口"——它是一个类似的概念,但在使用和实现上存在分歧。


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

如果您正在构建一个类,并且希望它像字典一样工作,那么您可以定义所有必需的__ __方法。但这有点痛苦。作为一种替代方法,您可以只定义几个,并从UserDict.DictMixin(在py3k中移动到collections.DictMixin)继承(除了任何其他继承之外)。这将自动定义dictionary api的所有其余部分。

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


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

在D中(顺便说一下,它不做MI),这是通过插入一个模板(考虑语法感知和安全的宏,你将关闭)到一个范围。这允许类、结构、函数、模块或任何东西中的一行代码扩展为任意数量的声明。


也许ruby中的一个例子可以帮助您:

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

1
2
3
4
5
6
<(other)
>(other)
==(other)
<=(other)
>=(other)
between?(other)

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

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


我只是使用了python mixin来实现python milters的单元测试。通常情况下,比较温和的人员会与MTA交谈,这使得单元测试非常困难。测试mixin覆盖与MTA通信的方法,并创建一个由测试用例驱动的模拟环境。

所以,你有一个未经修改的milter应用程序,像spfmilter,和mixin TestBase,像这样:

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
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='[email protected]')
  self.assertEqual(rc,Milter.CONTINUE)
  milter.close()

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


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

下面的示例解释了如何使用mixin实现多重继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module A    # you create a module
    def a1  # lets have a method 'a1' in it
    end
    def a2  # Another method 'a2'
    end
end

module B    # let's say we have another module
    def b1  # A method 'b1'
    end
    def b2  #another method b2
    end
end

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'
    end
end

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 A
samp.a2     # accessing method 'a2' from module A
samp.b1     # accessing method 'b1' from module B
samp.b2     # accessing method 'a2' from module B
samp.s1     # accessing method 's1' inside the class Sample


我听说你有c#背景。因此,一个好的起点可能是。net的mixin实现。

您可能想在http://remix.codeplex.com/上查看codeplex项目

观看lang.net专题讨论会链接以获得概述。还有更多关于codeplex页面的文档。

问候Stefan


OP提到他/她从未在c++中听说过mixin,这可能是因为在c++中它们被称为奇怪地重复出现的模板模式(CRTP)。另外,@Ciro Santilli提到mixin是通过c++中的抽象基类实现的。虽然抽象基类可以用来实现mixin,但它是一个过度的功能,因为在编译时使用模板可以实现虚拟函数的功能,而不需要在运行时进行虚拟表查找。

这里详细描述了CRTP模式

我使用下面的模板类将@Ciro Santilli的答案中的python例子转换成c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    #include <iostream>
    #include

    template <class T>
    class ComparableMixin {
    public:
        bool operator !=(ComparableMixin &amp;other) {
            return ~(*static_cast<T*>(this) == static_cast<T&amp;>(other));
        }
        bool operator <(ComparableMixin &amp;other) {
            return ((*(this) != other) &amp;&amp; (*static_cast<T*>(this) <= static_cast<T&amp;>(other)));
        }
        bool operator >(ComparableMixin &amp;other) {
            return ~(*static_cast<T*>(this) <= static_cast<T&amp;>(other));
        }
        bool operator >=(ComparableMixin &amp;other) {
            return ((*static_cast<T*>(this) == static_cast<T&amp;>(other)) || (*(this) > other));
        }
    };

    class Integer: public ComparableMixin<Integer> {
    public:
     Integer(int i) {
         this->i = i;
     }
     int i;
     bool operator <=(Integer &amp;other) {
         return (this->i <= other.i);
     }
     bool operator ==(Integer &amp;other) {
         return (this->i == other.i);
     }
protected:
    ComparableMixin() {}
    };

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;
}

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