在"编程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类并不是单独创建的。在更传统的多重继承中,
首先,您应该注意到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 |
这个特殊的例子可以通过
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方法。
它指出,如果一个类:
实现了然后该类免费获得一个
因此,至少在文档的这一点上,mixin不需要多重继承,并且与定义1相一致。
当然,文档在不同的地方可能是矛盾的,其他重要的Python库可能在其文档中使用其他定义。
这个页面还使用了术语
在其他语言
Ruby:很明显,mixin不需要多重继承,如编程Ruby和Ruby编程语言等主要参考书中所述
c++:未实现的方法是纯虚方法。
定义1与抽象类(具有纯虚方法的类)的定义一致。无法实例化该类。
定义2可以使用虚拟继承:两个派生类的多重继承
我认为它们是使用多重继承的一种有纪律的方法——因为mixin最终只是另一个python类,它(可能)遵循关于称为mixin的类的约定。
我对规范所谓Mixin的约定的理解是:
添加方法但不添加实例变量(类常量可以)仅继承自通过这种方式,它限制了多重继承的潜在复杂性,并通过限制必须查看的位置(与完整的多重继承相比),使跟踪程序流变得相当容易。它们类似于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),)
它从
这是重用代码的一种强大的方法,但也有问题。如果发现其中一个对象中有bug,不加注意地修复它可能会在子类中创建一个bug。
混合蛋白的例子mixin通常被推广为一种获得代码重用的方法,而不存在协作多继承(如OrderedCounter)可能存在的潜在耦合问题。当您使用mixin时,您使用的功能并不是与数据紧密耦合的。
与上面的示例不同,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):
passThe 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方法覆盖了
被覆盖的方法似乎是
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从另一个类中获取一些方法。但是在新风格的世界里,所有的东西,甚至是混合的东西,都继承自
有很多方法可以在Python中使多继承MRO工作,最显著的是super()函数,但这意味着您必须使用super()来完成整个类层次结构,而且理解控制流要困难得多。
我认为这里有一些很好的解释,但我想提供另一个视角。
在Scala中,您可以像这里描述的那样执行mixin,但是非常有趣的是,mixin实际上是"融合"在一起的,以创建一种新的类来继承。本质上,您不会从多个类/mixin继承,而是生成一种新的类,该类具有mixin要继承的所有属性。这是有道理的,因为Scala是基于JVM的,而JVM目前不支持多重继承(从Java 8开始)。
它暗示了一个类的定义方式:类NewClass用SecondMixin和ThirdMixin扩展FirstMixin…
我不确定CPython解释器是否执行相同的操作(mixin类组合),但我不会感到惊讶。此外,由于有c++背景,我不会将ABC或相当于mixin的"接口"称为ABC或"接口"——它是一个类似的概念,但在使用和实现上存在分歧。
也许举几个例子会有所帮助。
如果您正在构建一个类,并且希望它像字典一样工作,那么您可以定义所有必需的
第二个例子:GUI工具包wxPython允许您创建具有多个列的列表控件(例如,Windows资源管理器中的文件显示)。默认情况下,这些列表是相当基本的。您可以添加额外的功能,例如通过单击列标题,通过继承ListCtrl并添加适当的mixin,按特定列对列表排序。
它不是一个Python示例,但是在D编程语言中,术语"
在D中(顺便说一下,它不做MI),这是通过插入一个模板(考虑语法感知和安全的宏,你将关闭)到一个范围。这允许类、结构、函数、模块或任何东西中的一行代码扩展为任意数量的声明。
也许ruby中的一个例子可以帮助您:
您可以包含mixin
1 2 3 4 5 6 | <(other) >(other) ==(other) <=(other) >=(other) between?(other) |
它通过调用
如果两个对象相等,
我只是使用了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 &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对象时,受保护的构造函数将如何导致编译错误。