In Python Can One Implement Mixin Behavior Without Using Inheritance

In Python can one implement mixin behavior without using inheritance?

def mixer(*args):
"""Decorator for mixing mixins"""
def inner(cls):
for a,k in ((a,k) for a in args for k,v in vars(a).items() if callable(v)):
setattr(cls, k, getattr(a, k).im_func)
return cls
return inner

class Mixin(object):
def b(self): print "b()"
def c(self): print "c()"

class Mixin2(object):
def d(self): print "d()"
def e(self): print "e()"

@mixer(Mixin, Mixin2)
class Foo(object):
# Somehow mix in the behavior of the Mixin class,
# so that all of the methods below will run and
# the issubclass() test will be False.

def a(self): print "a()"

f = Foo()
f.a()
f.b()
f.c()
f.d()
f.e()
print issubclass(Foo, Mixin)

output:

a()
b()
c()
d()
e()
False

What is a mixin and why is it useful?

A mixin is a special kind of multiple inheritance. There are two main situations where mixins are used:

  1. You want to provide a lot of optional features for a class.
  2. You want to use one particular feature in a lot of different classes.

For an example of number one, consider werkzeug's request and response system. I can make a plain old request object by saying:

from werkzeug import BaseRequest

class Request(BaseRequest):
pass

If I want to add accept header support, I would make that

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
pass

If I wanted to make a request object that supports accept headers, etags, authentication, and user agent support, I could do this:

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

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

The difference is subtle, but in the above examples, the mixin classes weren't made to stand on their own. In more traditional multiple inheritance, the AuthenticationMixin (for example) would probably be something more like Authenticator. That is, the class would probably be designed to stand on its own.

How do I use Mixins without inheritance?

You have to inherit from another class if you want to use a mixin. However, you can simply inherit from Object:

class User extends Object with Persistence {
// implementation
}

But really, you can just inherit from Persistence as well which will have the same effect:

class User extends Persistance {
// implementation
}

Gilad Bracha explains that the syntax is specifically designed this way:

I think it is important to understand the semantic model here. "with"
is the mixin application operator, and it takes two parameters: a
superclass and a mixin, and yields a class. Saying "with Foo" in
isolation makes as much sense as saying >> 2 (you could interpret both
as curried functions, but that is very far from Dart). When you write
"C extends S with M", you are specifying a superclass following the
extends keyword, just as you do when you write "C extends K" except
that the superclass is not specified via an identifier but via a mixin
application. So the superclass would be "S with M".

As Lasse points out, as practical matter it doesn't restrict you, but
having the syntax reflect the underlying structure is important.

How do I dynamically add mixins as base classes without getting MRO errors?

Think of it this way -- you want the mixins to override some of the behaviors of object, so they need to be before object in the method resolution order.

So you need to change the order of the bases:

class C(A, B, object):
pass

Due to this bug, you need C not to inherit from object directly to be able to correctly assign to __bases__, and the factory really could just be a function:

class FakeBase(object):
pass

class C(FakeBase):
pass

def c_factory():
for base in (A, B):
if base not in C.__bases__:
C.__bases__ = (base,) + C.__bases__
return C()

Using a Mixin to Store Configs in a Class (with inheritance)

If you check the mixin example gCoh's link, you'll see that you need to add *args and **kwargs to the mixin to pass thru unused parameters. Also (seems counter-intuitive to me at least) you need to call super from the mixin not your Molecule class.

Is this the behavior you desire?

class DefaultsMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.memory = 4
self.threads = 2

class Molecule:
def __init__(self, name):
self.name = name

class Ligand(DefaultsMixin, Molecule):
def __init__(self, name):
super().__init__(name)
self.atoms = ['C', 'H']

mol = Ligand('methane')
print(f'The number of threads is {mol.threads}')
print(f'The atoms are {mol.atoms}')
print(f'The name is {mol.name}')

outputs:

The number of threads is 2
The atoms are ['C', 'H']
The name is methane

What's the pythonic way to add behavior to objects returned by an external module?

You can rewrite this in a more dynamic manner:

from other_module import Foo, FooTypeA, FooTypeB

bases = [Foo, FooTypeA, FooTypeB]

class MyMixin(object):
pass

def factory(bases, mixins, name='MyClass'):
return type(name, bases + mixins, {})

new_classes = [factory((c,), (MyMixin,)) for c in bases]


Related Topics



Leave a reply



Submit