代码之家  ›  专栏  ›  技术社区  ›  TarkaDaal

什么是混合,为什么它们有用?

  •  788
  • TarkaDaal  · 技术社区  · 17 年前

    在“ Programming Python ,马克·卢茨提到“混合”。我来自C/C++/C#背景,以前从未听说过这个词。什么是混合?

    字里行间的阅读 this example (我之所以链接到它,是因为它很长),我认为这是一种使用多重继承来扩展类的情况,而不是“正确”的子类化。是这样吗?

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

    15 回复  |  直到 10 年前
        1
  •  833
  •   dalore    8 年前

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

    作为一个例子,考虑一下 werkzeug's request and response system . 我可以通过以下方式创建一个普通的旧请求对象:

    from werkzeug import BaseRequest
    
    class Request(BaseRequest):
        pass
    

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

    from werkzeug import BaseRequest, AcceptMixin
    
    class Request(AcceptMixin, BaseRequest):
        pass
    

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

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

    区别很微妙,但在上面的例子中,mixin类并不是独立存在的。在更传统的多重继承中 AuthenticationMixin (例如)可能更像 Authenticator

        2
  •  272
  •   Randolpho    17 年前

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

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

    既然你问了,我想我应该谈谈原因。最大的好处是你不必一次又一次地自己做。在C#中,混搭的最大好处可能是 Disposal pattern . 无论何时实现IDisposable,您几乎总是希望遵循相同的模式,但最终编写并重新编写相同的基本代码,只是略有变化。如果有一个可扩展的处理混入,你可以节省大量额外的打字。

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

    是什么将mixin与多重继承区分开来的?这只是语义问题吗?

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

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

    同样,考虑一个已经实现的接口。

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

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

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

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

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

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

    如果您只是将mixin看作是一个小的基本类型,它旨在向一个类型添加少量功能,而不影响该类型,那么您就是黄金。

    希望如此。:)

        3
  •  214
  •   Julian    6 年前

    这个答案的目的是解释混音 举例说明 它们是:

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

    • 用Python

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

    还应考虑有争议的问题:

    多重继承是否是描述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是一种设计模式,其中基类的某个方法使用它未定义的方法,并且该方法将由 另一个基类

    术语 混合班 指打算在该设计模式中使用的基类(是指使用该方法的基类,还是指实现该方法的基类?)

    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 文档中明确使用了这个术语 混合方法 .

    它指出,如果一个类:

    • 工具 __next__
    • 从单个类继承 Iterator

    然后全班都会得到一个 __iter__ 混合法 免费的。

    因此,至少在文件的这一点上, ,并与定义1一致。

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

    本页还使用术语 Set mixin ,这清楚地表明 Set 迭代器

    用其他语言

    • Ruby:显然不需要mixin的多重继承,正如在主要参考书中提到的,例如 Programming Ruby

    • C++:A virtual 设置的方法 =0 这是一种纯粹的虚拟方法。

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

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

        4
  •  42
  •   Hamish Downer JKP    7 年前

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

    我对管理你称之为Mixin的事物的约定的理解是,Mixin:

    • 添加方法,但不添加实例变量(类常量正常)
    • 只继承自 object (Python语言)

    这样一来,它就限制了多重继承的潜在复杂性,并通过限制必须查看的位置(与完全多重继承相比),使跟踪程序流变得相当容易。它们类似于 ruby modules .

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

        5
  •  36
  •   Aaron Hall    9 年前

    是什么将mixin与多重继承区分开来的?这只是语义问题吗?

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

    您可能会说,“这只是多重继承,不是真正的mixin”,是因为可能会被混淆为mixin的类实际上可以被实例化和使用——因此它确实是一个语义上的、非常真实的差异。

    多重继承的例子

    from the documentation

    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 OrderedDict collections 单元

    二者都 柜台 旨在自行实例化和使用。然而,通过将它们都子类化,我们可以拥有一个计数器,该计数器被排序并重用每个对象中的代码。

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

    Mixin示例

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

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

    可以创建每种类型服务器的分叉和线程版本 在课堂上使用这些组合。例如,ThreadingUpServer是

    class ThreadingUDPServer(ThreadingMixIn, UDPServer):
        pass
    

    mix-in类位于第一位,因为它重写了中定义的方法 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()
    

    做作的例子

    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
  •  30
  •   lakshmen    14 年前

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

    我看了这个视频 http://www.youtube.com/watch?v=v_uKI2NOLEM

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

        7
  •  19
  •   Tomasz Bartkowiak    6 年前

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

    1.抽象类

    • 抽象类 可以

    2.界面

    • 界面 包含抽象方法 只有 (没有非抽象方法和内部状态)

    3.混血儿

    • (如接口) 不要 包含内部状态(实例变量)
    • 混血儿 包含一个或多个非抽象方法(它们是 包含非抽象方法(不同于接口)

    class 锿。然而,两者的共同特点是 抽象类、接口 混血儿 是他们吗 独立存在,即不应实例化。

        8
  •  11
  •   SilentDirge    11 年前

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

    在Scala中,您可以像这里所描述的那样进行混合,但非常有趣的是,这些混合实际上是“融合”在一起的,以创建一种新的类来继承。本质上,您不会从多个类/mixin继承,而是生成一个新的类,该类包含要从中继承的mixin的所有属性。这是有意义的,因为Scala基于JVM,目前不支持多重继承(从Java8开始)。顺便说一下,这个mixin类类型是Scala中称为Trait的特殊类型。

    它以类的定义方式暗示: ...

    我不确定CPython解释器是否也执行相同的操作(mixin类组合),但我不会感到惊讶。另外,从C++背景来看,我不把ABC或“接口”称为混合函数——它是一个类似的概念,但在使用和实现上是不同的。

        9
  •  9
  •   bobince    17 年前

    我建议不要在新的Python代码中混入,如果你能找到其他的解决方法(比如组合而不是继承,或者只是在你自己的类中使用猴子补丁方法),那就不用花太多精力了。

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

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

        10
  •  9
  •   John Fouhy    17 年前

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

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

        11
  •  9
  •   BCS    14 年前

    the D programing language mixin 是指一个结构,其使用方式与此基本相同;向类中添加一堆内容。

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

        12
  •  7
  •   bigdata2    7 年前

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

    here

    我使用下面的模板类将@ 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;
    }
    

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

        13
  •  6
  •   Georg Schölly Crazy Developer    17 年前

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

    你可以把调味品包括在内 Comparable "<=>(other)" ,mixin提供所有这些功能:

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

    它通过调用 <=>(other) 并返回正确的结果。

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

        14
  •  6
  •   Ernest Friedman-Hill    14 年前

    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
    
        15
  •  6
  •   Stuart Gathman    13 年前

    我刚刚使用了一个pythonmixin来实现pythonmilter的单元测试。通常,milter会与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)
    

    然后,在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=markup

        16
  •  3
  •   Stefan Papp    15 年前

    您可能希望在以下位置查看codeplex项目: http://remix.codeplex.com/

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

    当做 斯特凡