在Python编程中,Mark Lutz提到了术语mixin。我有C/ c++ / c#的背景,以前没有听说过这个术语。什么是mixin?

从这个例子的字里行间(我链接了这个例子,因为它很长),我假设这是一个使用多重继承来扩展类的情况,而不是适当的子类化。这样对吗?

为什么我要这样做,而不是把新功能放到一个子类?就此而言,为什么混合/多重继承方法会比使用组合更好呢?

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


当前回答

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

您希望为类提供大量可选特性。 你想在很多不同的类中使用一个特定的特性。

对于第一个例子,考虑werkzeug的请求和响应系统。我可以这样创建一个普通的request对象:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

如果我想添加接受报头支持,我会这样做

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

如果我想让一个请求对象支持接受头,标签,身份验证和用户代理支持,我可以这样做:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

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

区别是微妙的,但是在上面的例子中,mixin类并不是独立存在的。在更传统的多重继承中,AuthenticationMixin(例如)可能更像Authenticator。也就是说,这个类可能被设计成独立存在的。

其他回答

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

OP提到他/她从未听说过c++中的mixin,可能是因为它们在c++中被称为奇怪的重复模板模式(CRTP)。另外,@Ciro Santilli提到,mixin在c++中是通过抽象基类实现的。虽然可以使用抽象基类来实现mixin,但这是一种过度使用,因为运行时的虚函数功能可以在编译时使用模板来实现,而不需要在运行时查找虚表。

这里将详细描述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));
        }
        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对象时protected构造函数将如何导致编译错误。

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

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

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

你可能会说,“这只是多重继承,而不是真正的mixin”,因为可能被混淆为mixin的类实际上可以被实例化和使用——所以这确实是语义上的,而且非常真实的区别。

多重继承示例

这个例子,来自文档,是一个OrderedCounter:

类OrderedCounter(计数器,OrderedDict): “计数器,记住第一次遇到的顺序元素” def __repr__(自我): 返回'%s(%r)' % (self.__class__. zip)。__name__ OrderedDict(自我) def __reduce__(自我): 回归自我。__class__进行(OrderedDict(自我),)

它从集合模块继承了Counter和OrderedDict。

Counter和OrderedDict都打算被实例化并单独使用。然而,通过将它们都子类化,我们可以拥有一个有序的计数器,并重用每个对象中的代码。

这是一种重用代码的强大方法,但也可能产生问题。如果发现其中一个对象存在bug,那么不加注意地修复它可能会在子类中创建一个bug。

Mixin的例子

mixin通常被推广为一种获得代码重用的方式,而不会出现协作多重继承(如OrderedCounter)可能存在的潜在耦合问题。当您使用mixin时,您使用的功能与数据不是紧密耦合的。

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

例如,标准库在socketserver库中有两个mixin。

可以创建每种类型服务器的分叉和线程版本 使用这些混合类。例如,ThreadingUDPServer是 创建如下: 类ThreadingMixIn, UDPServer: 通过 mix-in类首先出现,因为它覆盖了定义在 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)

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

如果您正在构建一个类,并且希望它像字典一样工作,那么您可以定义所有必要的__ __方法。但这有点麻烦。作为一种替代方法,您可以只定义一些,并从UserDict继承(除了任何其他继承)。DictMixin(移动到集合。py3k中的DictMixin)。这将自动定义字典api的所有其余部分。

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

这个概念来自Steve 's Ice Cream,这是一家由Steve Herrell于1973年在马萨诸塞州萨默维尔创立的冰淇淋店,在那里混合(糖果、蛋糕等)被混合成基本的冰淇淋口味(香草、巧克力等)。

受到Steve 's Ice Cream的启发,Lisp对象系统Flavors的设计者第一次在编程语言中包含了这个概念,其中mix-in是用于增强其他类的小型辅助类,flavour是大型独立类。

因此,主要的思想是mix-in是一个可重用的扩展(“可重用”而不是“独占”;' extension '而不是' base ')。

这个概念与单一或多个继承以及抽象或具体类的概念是正交的。混合类可以用于单个或多个继承,可以是抽象类也可以是具体类。混合类有不完整的接口,抽象类有不完整的实现,具体类有完整的实现。

Mix-in类名通常以' -MixIn '、' -able '或' -ible '作为后缀,以强调它们的性质,就像Python标准库中socketserver模块的ThreadingMixIn和ForkingMixIn类,以及集合的Hashable、Iterable、Callable、Awaitable、AsyncIterable和可逆类一样。美国广播公司(abc)模块。

下面是一个mix-in类的例子,用于扩展Python内置list和dict类的日志记录功能:

import logging

class LoggingMixIn:
    def __setitem__(self, key, value):
        logging.info('Setting %r to %r', key, value)
        super().__setitem__(key, value)
    def __delitem__(self, key):
        logging.info('Deleting %r', key)
        super().__delitem__(key)

class LoggingList(LoggingMixIn, list):
    pass

class LoggingDict(LoggingMixIn, dict):
    pass
>>> logging.basicConfig(level=logging.INFO)
>>> l = LoggingList([False])
>>> d = LoggingDict({'a': False})
>>> l[0] = True
INFO:root:Setting 0 to True
>>> d['a'] = True
INFO:root:Setting 'a' to True
>>> del l[0]
INFO:root:Deleting 0
>>> del d['a']
INFO:root:Deleting 'a'