What is a mixin, and why are they useful?

后端 未结 16 2127
無奈伤痛
無奈伤痛 2020-11-22 00:18

In \"Programming Python\", Mark Lutz mentions \"mixins\". I\'m from a C/C++/C# background and I have not heard the term before. What is a mixin?

Reading between the

16条回答
  •  无人及你
    2020-11-22 01:09

    What separates a mixin from multiple inheritance? Is it just a matter of semantics?

    A mixin is a limited form of multiple inheritance. In some languages the mechanism for adding a mixin to a class is slightly different (in terms of syntax) from that of inheritance.

    In the context of Python especially, a mixin is a parent class that provides functionality to subclasses but is not intended to be instantiated itself.

    What might cause you to say, "that's just multiple inheritance, not really a mixin" is if the class that might be confused for a mixin can actually be instantiated and used - so indeed it is a semantic, and very real, difference.

    Example of Multiple Inheritance

    This example, from the documentation, is an 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),)
    

    It subclasses both the Counter and the OrderedDict from the collections module.

    Both Counter and OrderedDict are intended to be instantiated and used on their own. However, by subclassing them both, we can have a counter that is ordered and reuses the code in each object.

    This is a powerful way to reuse code, but it can also be problematic. If it turns out there's a bug in one of the objects, fixing it without care could create a bug in the subclass.

    Example of a Mixin

    Mixins are usually promoted as the way to get code reuse without potential coupling issues that cooperative multiple inheritance, like the OrderedCounter, could have. When you use mixins, you use functionality that isn't as tightly coupled to the data.

    Unlike the example above, a mixin is not intended to be used on its own. It provides new or different functionality.

    For example, the standard library has a couple of mixins in the socketserver library.

    Forking and threading versions of each type of server can be created using these mix-in classes. For instance, ThreadingUDPServer is created as follows:

    class ThreadingUDPServer(ThreadingMixIn, UDPServer):
        pass
    

    The 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.

    In this case, the mixin methods override the methods in the UDPServer object definition to allow for concurrency.

    The overridden method appears to be process_request and it also provides another method, process_request_thread. Here it is from the 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()
    

    A Contrived Example

    This is a mixin that is mostly for demonstration purposes - most objects will evolve beyond the usefulness of this 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)
              )
    

    and usage would be:

    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__()
    

    And usage:

    >>> f1 = Foo('bar')
    >>> f2 = Foo()
    >>> f1
    Foo(foo='bar')
    >>> f2
    Foo(foo=None)
    

提交回复
热议问题