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



> - 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] (the "diamond problem"), 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?

http://stackoverflow.com/questions/533631/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.


```python
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)...`


```python
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.

```python
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())


```