在" 编程Python "中,Mark Lutz提到了"mixins".我来自C/C++/C#背景,我之前没有听过这个词.什么是mixin?
在这个例子的行之间进行读取(我已经链接到了因为它很长),我假设这是一个使用多重继承来扩展类而不是"正确"子类的情况.这是正确的吗?
为什么我要这样做而不是将新功能放入子类?就此而言,为什么mixin/multiple继承方法比使用组合更好?
mixin与多重继承的区别是什么?这仅仅是语义问题吗?
mixin是一种特殊的多重继承.使用mixins有两种主要情况:
您希望为类提供许多可选功能.
您想在许多不同的类中使用一个特定的功能.
举一个例子,考虑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
差异很微妙,但在上面的例子中,mixin类并不是独立的.在更传统的多重继承中,AuthenticationMixin
(例如)可能更像是Authenticator
.也就是说,该课程可能会设计为独立.
首先,您应该注意mixins仅存在于多继承语言中.你不能用Java或C#做mixin.
基本上,mixin是一种独立的基类型,为儿童类提供有限的功能和多态共振.如果您正在考虑使用C#,请考虑一个您不必实际实现的接口,因为它已经实现了; 您只需从中继承并从其功能中受益.
混合物的范围通常较窄,并不意味着延长.
[编辑 - 为什么:]
我想我应该解释为什么,因为你问过.最大的好处是你不必一次又一次地自己做.在C#中,mixin可能受益的最大地方可能来自Disposal模式.每当你实现IDisposable时,你几乎总是想要遵循相同的模式,但是你最终会编写并重新编写相同的基本代码并进行微小的变化.如果有一个可扩展的Disposal mixin,你可以节省很多额外的打字.
[编辑2 - 回答你的其他问题]
mixin与多重继承的区别是什么?这仅仅是语义问题吗?
是.mixin和标准多重继承之间的区别仅仅是语义问题; 具有多重继承的类可以使用mixin作为该多重继承的一部分.
mixin的目的是创建一种类型,可以通过继承"混入"到任何其他类型,而不会影响继承类型,同时仍然为该类型提供一些有益的功能.
再想想一个已经实现的接口.
我个人不使用mixins,因为我主要用不支持它的语言开发,所以我很难想出一个体面的例子来提供"ahah!" 你的时刻.但我会再试一次.我将使用一个被设计的例子 - 大多数语言已经以某种方式提供了这个特性 - 但希望能解释如何创建和使用mixins.开始:
假设您有一个类型,您希望能够序列化到XML或从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视为一种小型基本类型,旨在为类型添加少量功能而不会影响该类型,那么您就是金色的.
希望.:)
这个答案旨在用以下示例来解释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.你必须考虑作者的意图.
这种模式很有意思,因为它可以重新组合具有不同基类选择的功能:
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出现
在collections.abc的官方文档中,文档明确使用术语Mixin方法.
它声明如果一个类:
器物 __next__
继承自单个类 Iterator
然后该类免费获得__iter__
mixin方法.
因此,至少在文档的这一点上,mixin不需要多重继承,并且与定义1一致.
文档当然可以在不同的点上相互矛盾,其他重要的Python库可能在其文档中使用其他定义.
这个页面也使用了这个术语Set mixin
,它清楚地表明类Set
和的类Iterator
可以称为Mixin类.
在其他语言中
Ruby:显然不需要mixin的多重继承,如编程Ruby和Ruby编程语言等主要参考书中所提到的
C++:未实现的方法是纯虚方法.
定义1与抽象类(具有纯虚方法的类)的定义一致.该类无法实例化.
使用虚拟继承可以实现定义2:来自两个派生类的多重继承
我认为它们是使用多重继承的一种规范方式 - 因为最终mixin只是另一个python类(可能)遵循有关被称为mixins的类的约定.
我对那些你称之为Mixin的约定的理解是一个混合:
添加方法但不添加实例变量(类常量可以)
只继承自object
(在Python中)
这样就限制了多重继承的潜在复杂性,并且通过限制您必须查看的位置(与完全多重继承相比),可以非常轻松地跟踪程序流.它们类似于ruby模块.
如果我想添加实例变量(具有比单继承所允许的更大的灵活性),那么我倾向于使用组合.
话虽如此,我已经看到了名为XYZMixin的类,它们有实例变量.
Mixins是编程中的一个概念,其中类提供了功能,但它并不意味着用于实例化.Mixins的主要目的是提供独立的功能,如果mixin本身不具有与其他mixin的继承并且还避免状态,那将是最好的.在诸如Ruby之类的语言中,有一些直接的语言支持,但对于Python,则没有.但是,您可以使用多类继承来执行Python中提供的功能.
我观看了这段视频http://www.youtube.com/watch?v=v_uKI2NOLEM,了解mixins的基础知识.对于初学者来说,了解mixin的基础知识及其工作原理以及实现它们可能遇到的问题非常有用.
维基百科仍然是最好的:http://en.wikipedia.org/wiki/Mixin
mixin与多重继承的区别是什么?这仅仅是语义问题吗?
mixin是一种有限形式的多重继承.在某些语言中,将mixin添加到类中的机制与继承的机制略有不同(就语法而言).
特别是在Python的上下文中,mixin是一个父类,它为子类提供功能,但不打算自己实例化.
可能会让你说,"那只是多重继承,而不是真正的混合",如果可能混淆了mixin的类实际上可以被实例化和使用 - 所以它确实是一个语义,非常真实的差异.
这个例子来自文档,是一个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
和OrderedDict
从collections
模块.
双方Counter
并OrderedDict
意图被实例化,并在自己使用.但是,通过对它们进行子类化,我们可以拥有一个有序的计数器,并重用每个对象中的代码.
这是重用代码的有效方法,但它也可能存在问题.如果事实证明其中一个对象存在错误,那么无需小心修复就可能在子类中创建一个错误.
Mixins通常被提升为获得代码重用的方式,而没有合作多重继承(如OrderedCounter)可能具有的潜在耦合问题.使用mixins时,使用的功能与数据紧密耦合.
与上面的示例不同,mixin不打算单独使用.它提供新的或不同的功能.
例如,标准库有一对夫妇在混入socketserver
库.
可以使用这些混合类创建每种类型服务器的分叉和线程版本.例如,ThreadingUDPServer创建如下:
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass混合类首先出现,因为它覆盖了UDPServer中定义的方法.设置各种属性还会更改基础服务器机制的行为.
在这种情况下,mixin方法会覆盖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 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()
这是一个主要用于演示目的的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)
我认为这里有一些很好的解释,但我想提供另一种观点.
在Scala中,你可以像这里描述的那样进行mixins,但是非常有趣的是mixins实际上是"融合"在一起创建一种新类继承.本质上,您不是从多个类/ mixin继承,而是生成一种新类,其中包含要继承的mixin的所有属性.这是有道理的,因为Scala基于JVM,其中当前不支持多继承(从Java 8开始).顺便说一下,这个mixin类类型是一种特殊类型,称为Scala中的Trait.
它在定义类的方式中暗示:类NewClass使用ThirdMixin将SecondMixin扩展为FirstMixin ...
我不确定CPython解释器是否也这样做(mixin类组合),但我不会感到惊讶.此外,来自C++背景,我不会称ABC或"接口"等同于mixin - 它是一个类似的概念,但在使用和实现上有所不同.
我建议不要使用新的Python代码中的混合,如果你可以找到任何其他方法(比如组合 - 而不是继承,或者只是猴子修补方法到你自己的类中),那就不多了努力.
在旧式类中,您可以使用mix-ins作为从另一个类中获取一些方法的方法.但在新风格的世界里,一切,甚至是混合,都继承了object
.这意味着任何使用多重继承自然会引入MRO问题.
有一些方法可以在Python中使用多继承MRO,最值得注意的是super()函数,但这意味着你必须使用super()来完成整个类层次结构,并且理解控制流程要困难得多.
也许有几个例子会有所帮助.
如果您正在构建一个类,并且希望它像字典一样运行,则可以定义__ __
所需的所有各种方法.但这有点痛苦.作为替代方案,您可以定义一些,并继承(除了任何其他继承)UserDict.DictMixin
(移动到collections.DictMixin
py3k).这将具有自动定义字典api的所有其余部分的效果.
第二个例子:GUI工具包wxPython允许您创建具有多个列的列表控件(例如,Windows资源管理器中的文件显示).默认情况下,这些列表非常基本.您可以添加其他功能,例如通过单击列标题按特定列对列表进行排序,继承ListCtrl并添加适当的mixin.
它不是Python示例,但在D编程语言中,该术语mixin
用于指代以相同方式使用的构造; 在课堂上添加一堆东西.
在D中(顺便说一下,它不做MI)这是通过将一个模板(认为语法识别和安全的宏,你将会很接近)插入一个范围来完成的.这允许在类,结构,函数,模块或其他任何内容中的单行代码扩展到任意数量的声明.
也许ruby的一个例子可以帮助:
你可以包含mixin Comparable
并定义一个函数"<=>(other)"
,mixin提供所有这些功能:
<(other) >(other) ==(other) <=(other) >=(other) between?(other)
它通过调用<=>(other)
和回馈正确的结果来实现这一点.
"instance <=> other"
返回0,如果两个对象是相等的,小于0,如果instance
是比更大other
和超过0,如果other
是更大的.
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
我只是使用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
OP提到他/她从未听说过C ++中的mixin,也许是因为它们在C ++中被称为“好奇重复模板模式(CRTP)”。另外,@ Ciro Santilli提到mixin是通过C ++中的抽象基类实现的。尽管可以使用抽象基类来实现mixin,但这是一个过高的选择,因为可以在编译时使用模板在运行时实现虚拟功能的功能,而无需在运行时查找虚拟表。
此处详细描述了CRTP模式
我已经使用以下模板类将@Ciro Santilli的答案中的python示例转换为C ++:
#include#include template class ComparableMixin { public: bool operator !=(ComparableMixin &other) { return ~(*static_cast (this) == static_cast (other)); } bool operator <(ComparableMixin &other) { return ((*(this) != other) && (*static_cast (this) <= static_cast (other))); } bool operator >(ComparableMixin &other) { return ~(*static_cast (this) <= static_cast (other)); } bool operator >=(ComparableMixin &other) { return ((*static_cast (this) == static_cast (other)) || (*(this) > other)); } protected: ComparableMixin() {} }; class Integer: public ComparableMixin { 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 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对象时受保护的构造函数将如何导致编译错误。