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

时间:2009-02-10 18:50:42

标签: python oop multiple-inheritance mixins

在“Programming Python”中,Mark Lutz提到“mixins”。我来自C / C ++ / C#背景,我之前没有听过这个词。什么是mixin?

this example之间进行读取(由于它很长,我已经链接到了它),我假设使用多重继承来扩展类而不是“正确”的子类化。这是正确的吗?

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

mixin与多重继承的区别是什么?这仅仅是语义问题吗?

16 个答案:

答案 0 :(得分:606)

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

  1. 您希望为课程提供许多可选功能。
  2. 您想在许多不同的类中使用一个特定功能。
  3. 对于第一个例子,请考虑werkzeug's request and response system。我可以通过说:

    来创建一个普通的旧请求对象
    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。也就是说,这门课可能会设计成独立的。

答案 1 :(得分:205)

首先,您应该注意mixins仅存在于多继承语言中。你不能用Java或C#做mixin。

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

混合物的范围通常很窄,并不意味着延长。

[编辑 - 至于原因:]

我想我应该解释为什么,因为你问过。最大的好处是你不必一次又一次地自己做。在C#中,mixin可以受益的最大的地方可能来自Disposal pattern。每当你实现IDisposable时,你几乎总是想要遵循相同的模式,但是你最终会编写并重新编写相同的基本代码并进行微小的变化。如果有一个可扩展的Disposal mixin,你可以节省很多额外的打字。

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

  

mixin与多重继承的区别是什么?这仅仅是语义问题吗?

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

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

再次,想一想已经实现的接口。

我个人不使用mixins,因为我主要用一种不支持它的语言开发,所以我很难找到一个体面的例子来提供“ahah!”你的时刻。但我会再试一次。我将使用一个被设计的例子 - 大多数语言已经以某种方式提供了这个特性 - 但希望能解释如何创建和使用mixins。这是:

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

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

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

而且......就是这样。要使用它,您可以将任何需要序列化为XML的类型继承自XmlSerializable。无论何时需要序列化或反序列化该类型,您只需调用ToXML或FromXML即可。事实上,由于XmlSerializable是一个完全成熟的类型和多态,你可以设想构建一个文档序列化程序,它不知道你的原始类型,只接受一个XmlSerializable类型的数组。

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

如果您只是将mixin视为一种小型基本类型,旨在为类型添加少量功能而不会影响该类型,那么您就是金色的。

希望。 :)

答案 2 :(得分:143)

这个答案旨在解释mixins 以及的例子

  • 自包含:简短,无需了解任何库即可理解该示例。

  • Python中的 ,而不是其他语言。

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

还应考虑有争议的问题:

  

是否需要多重继承来表征mixin?

<强>解释

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

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

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

定义1:没有多重继承

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

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

某些来源添加的约束是该类可能不包含数据,只包含方法,但我不明白为什么这是必要的。然而,在实践中,许多有用的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使用该方法的那些,或实现它的那些?)

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

这种模式很有意思,因为它可以重新组合具有不同基类选择的功能:

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事件

official documentatiton for collections.abc,文档明确使用术语 Mixin方法

它声明如果一个类:

  • 实施__next__
  • 继承自单个班级Iterator

然后该课程免费获得__iter__ mixin方法

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

文档当然可以在不同的点上相互矛盾,其他重要的Python库可能正在使用其文档中的其他定义。

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

其他语言

  • Ruby:显然不需要mixin的多重继承,如Programming Ruby和Ruby编程语言

  • 等主要参考书中所提到的
  • C ++:未实现的方法是纯虚方法。

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

    使用虚拟继承可以定义2:Multiple Inheritance from two derived classes

答案 3 :(得分:31)

我认为它们是使用多重继承的一种规范方式 - 因为最终mixin只是另一个python类(可能)遵循有关被称为mixins的类的约定。

我对管理你称之为Mixin的约定的理解是一个Mixin:

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

这样就限制了多重继承的潜在复杂性,并且通过限制您必须查看的位置(与完全多重继承相比),可以非常轻松地跟踪程序流。它们类似于ruby modules

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

话虽如此,我见过名为XYZMixin的类,它们有实例变量。

答案 4 :(得分:26)

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

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

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

答案 5 :(得分:21)

  

mixin与多重继承的区别是什么?这仅仅是语义问题吗?

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

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

可能会让你说,“那只是多重继承,而不是真正的混合”,如果可能混淆了mixin的类实际上可以被实例化和使用 - 所以它确实是一个语义,而且非常真实,差。

多重继承的示例

此示例from the documentation是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),)

它是Counter模块中OrderedDictcollections的子类。

CounterOrderedDict都旨在实例化并单独使用。但是,通过对它们进行子类化,我们可以拥有一个有序的计数器,并重用每个对象中的代码。

这是重用代码的有效方法,但它也可能存在问题。如果事实证明其中一个对象存在错误,那么无需小心修复就可能在子类中创建一个错误。

Mixin的例子

Mixins通常被提升为获得代码重用的方式,而没有合作多重继承(如OrderedCounter)可能具有的潜在耦合问题。使用mixins时,使用的功能与数据紧密耦合。

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

例如,标准库有几个mixins in the socketserver library

  

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

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass
     

混合类首先出现,因为它覆盖了一个定义的方法   UDPServer。设置各种属性也会改变其行为   底层服务器机制。

在这种情况下,mixin方法会覆盖UDPServer对象定义中的方法以允许并发。

重写的方法似乎是process_request,它还提供了另一种方法process_request_thread。这是来自source code

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()

A Contrived Example

这是一个主要用于演示目的的mixin - 大多数对象将超越此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)

答案 6 :(得分:10)

我建议不要使用新的Python代码中的混合,如果你能找到任何其他方法(比如组合 - 代替继承,或者只是将猴子修补方法放入你自己的类中),那就不是更多的努力。

在旧式课程中,您可以使用混合作为从另一个类中获取一些方法的方法。但在新风格的世界中,一切,甚至是混合,都来自object。这意味着任何多重继承的使用自然会引入MRO issues

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

答案 7 :(得分:10)

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

在Scala中,你可以像这里描述的那样做mixins,但是非常有趣的是mixins实际上是“融合”在一起创建一种新类继承。本质上,您不是从多个类/ mixin继承,而是生成一种新类,其中包含要继承的mixin的所有属性。这是有道理的,因为Scala基于JVM,其中当前不支持多继承(从Java 8开始)。顺便说一下,这个mixin类类型是一种称为Scala中Trait的特殊类型。

它在定义类的方式中暗示:     class NewClass使用ThirdMixin扩展FirstMixin和SecondMixin     ...

我不确定CPython解释器是否也这样做(mixin类组合),但我不会感到惊讶。此外,来自C ++背景,我不会将ABC或“界面”称为mixin - 这是一个类似的概念,但在使用和实现方面存在分歧。

答案 8 :(得分:10)

我认为以前的回答很好地定义了 MixIns 是什么。然而, 为了更好地理解它们,将 MixIns 抽象类 <从代码/实现的角度来看em> Interfaces

1。抽象类

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

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

2。界面

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

3。 MixIns

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

例如Python这些只是约定,因为以上所有内容均定义为class es。但是,抽象类,接口 MixIns 的共同特征是它们不应该独立存在,即不应实例化。

答案 9 :(得分:8)

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

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

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

答案 10 :(得分:7)

这不是Python示例,但在the D programing language中,术语mixin用于指代大致使用相同的构造;在课堂上添加一堆东西。

在D中(顺便说一下,它不做MI)这是通过将一个模板(认为语法识别和安全的宏,你会很接近)插入一个范围来完成的。这允许在类,结构,函数,模块或其他任何内容中使用单行代码扩展为任意数量的声明。

答案 11 :(得分:6)

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

您可以添加mixin Comparable并定义一个函数"<=>(other)",mixin提供所有这些功能:

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

通过调用<=>(other)并回馈正确的结果来实现此目的。

如果两个对象相等,则

"instance <=> other"返回0;如果instance大于other,则小于0;如果other更大,则小于0。

答案 12 :(得分:5)

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

这是一个例子,解释了如何使用mixin实现多重继承。

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

答案 13 :(得分:5)

我刚用python mixin为python milters实现单元测试。通常情况下,米尔特会谈MTA,使单元测试变得困难。测试mixin覆盖了与MTA对话的方法,并创建了由测试用例驱动的模拟环境。

所以,你需要一个未经修改的milter应用程序,比如spfmilter,和mixin 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)

然后,在测试用例中使用TestMilter进行milter应用:

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=markup

答案 14 :(得分:3)

我读到你有一个c#背景。因此,一个好的起点可能是.NET的mixin实现。

您可能希望在http://remix.codeplex.com/

查看codeplex项目

观看lang.net Symposium链接以获取概述。关于codeplex页面的文档还有很多。

问候 斯特凡

答案 15 :(得分:3)

OP提到他/她从来没有听说过C ++中的mixin,也许是因为它们在C ++中被称为Curiously Recurring Template Pattern(CRTP)。另外,@ Santi Santilli提到mixin是通过C ++中的抽象基类实现的。虽然抽象基类可用于实现mixin,但由于在运行时使用模板可以在运行时使用模板实现虚拟函数的功能而无需在运行时查找虚拟表的开销,因此它是一种过度杀伤。

详细描述了here

中的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));
        }
    };

    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);
     }
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的对象时受保护的构造函数将如何导致编译错误。