协慌网

登录 贡献 社区

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

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

这个例子的行之间进行读取(我已经链接到了因为它很长),我假设这是一个使用多重继承来扩展类而不是 “正确” 子类的情况。这是正确的吗?

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

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

答案

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

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

举一个例子,考虑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 ,它清楚地表明像SetIterator这样的类可以称为 Mixin 类。

在其他语言中

  • Ruby:显然不需要 mixin 的多重继承,如编程 Ruby和 Ruby 编程语言等主要参考书中所提到的

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

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

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