LoginSignup
2
1

More than 5 years have passed since last update.

EP 26 Use Multiple Inheritance Only for Mix-in Utility Classes

Posted at
  • Avoid using multiple inheritance if mix-in classes can achieve the same outcome.
  • Use pluggable behaviors at the instance level to provide per-class customization when mix-in classes may require it.
  • Compose mix-ins to create complex functionality from simeple behaviors.

Effective Python

Mix-in

Mix-in is a small class that only define a set of additional methods that a class should provide.

In object-oriented programming languages, a mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes. How those other classes gain access to the mixin's methods depends on the language. Mixins are sometimes described as being "included" rather than "inherited".

Mixins encourage code reuse and can be used to avoid the inheritance ambiguity that multiple inheritance can cause 1, or to work around lack of support for multiple inheritance in a language. A mixin can also be viewed as an interface with implemented methods. This pattern is an example of enforcing the dependency inversion principle.

https://en.wikipedia.org/wiki/Mixin

What is a mixin, and why are they useful?

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

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

import time

class ToDictMixin:
    def to_dict(self):
        return self._traverse_dict(self.__dict__)

    def _traverse_dict(self, instance_dict):
        output = {}
        for key, value in instance_dict.items():
            output[key] = self._traverse(key, value)
        return output

    def _traverse(self, key, value):
        time.sleep(0.2)
        if isinstance(value, ToDictMixin):
            print('ToDictMixin')
            return value.to_dict()
        elif isinstance(value, dict):
            print('dict')
            return self._traverse_dict(value)
        elif isinstance(value, list):
            print('list')
            return [self._traverse(key, i) for i in value]
        elif hasattr(value, '__dict__'):
            print('hasattr __dict__')
            return self._traverse_dict(value.__dict__)
        else:
            return value

class BinaryTree(ToDictMixin):
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

tree = BinaryTree(10, left=BinaryTree(7, right=BinaryTree(9)),
        right=BinaryTree(13, left=BinaryTree(11)))

# print(tree.to_dict())

Without overiding _traverse, BinaryTreeWithParent causes stack overflow by infinite loop.

to_dict -> _traverse_dict -> _traverse(isinstance of ToDictMixin) -> to_dict -> _travese_dict -> _traverse(isinstance of ToDitctMixin)...

class BinaryTreeWithParent(BinaryTree):
    def __init__(self, value, left=None, right=None, parent=None):
        super().__init__(value, left=left, right=right)
        self.parent = parent

    def _traverse(self, key, value):
        if (isinstance(value, BinaryTreeWithParent) and key == 'parent'):
            return value.value
        else:
            return super()._traverse(key, value)

root = BinaryTreeWithParent(10)
root.left = BinaryTreeWithParent(7, parent=root)
root.left.right = BinaryTreeWithParent(9, parent=root.left)
print(root.to_dict())

NamedSubTree also inherits ToDictMixin. However _travese behavior of BinaryTreeWithParent is not changed due to the polymorphism.

class NamedSubTree(ToDictMixin):
    def __init__(self, name, tree_with_parent):
        self.name = name
        self.tree_with_parent = tree_with_parent

my_tree = NamedSubTree('foobar', root)
print(my_tree.to_dict())


2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1