Effective Python 学習備忘録 12日目 【12/100】

# はじめに

Twitterで一時期流行していた 100 Days Of Code なるものを先日知りました。本記事は、初学者である私が100日の学習を通してどの程度成長できるか記録を残すこと、アウトプットすることを目的とします。誤っている点、読みにくい点多々あると思います。ご指摘いただけると幸いです！

# 今回学習する教材

- [Effective Python](https://www.amazon.co.jp/Effective-Python-%E2%80%95Python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%92%E6%94%B9%E8%89%AF%E3%81%99%E3%82%8B59%E9%A0%85%E7%9B%AE-Brett-Slatkin/dp/4873117569/ref=sr_1_3?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=Effective+python&qid=1609408832&sr=8-3)

    - 8章構成
    - 本章216ページ

# 今日の進捗

- 進行状況：73-78ページ
- 第3章：クラスと継承
- 本日学んだことの中で、よく忘れるところ、知らなかったところを書いていきます。

## 多重継承はmix-inユーティリティクラスだけに使う

多重継承はあまり使うべきではなく、代わりにmix-inを使うべきです。

### mix-inとは

クラスが提供すべき一連の追加のメソッドを定義するだけの小さなクラスのことです。
また、通常のクラスと異なり、インスタンス属性を持たず、`__init__`コンストラクタを呼び出す必要もないです。

mix-inの例を、継承した任意のクラスで追加される新たなメソッドとして次のように定義します。

```python
class ToDictMixin(object):
    def to_dict(self):
        '''
        このオブジェクトの属性を辞書にして返す
        '''

        return self._traverse_dict(self.__dict__)

    def _traverse_dict(self, instance_dict):
        '''
        辞書を受け取り、新たな辞書outputを返す。

        Parameters
        ----------
        instance_dict: dict
        
        Returns
        -------
        output : dict
            キーに、instance_dictのキー、値に_traverse()
        '''

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

    def _traverse(self, key, value):
        '''
        辞書の値の型に応じて、関数を呼び出す。
        '''
        
        if isinstance(value, ToDictMixin):
            return value.to_dict()
        elif isinstance(value, dict):
            return self._traverse_dict(value)
        elif isinstance(value, list):
            return [self._traverse(key, i) for i in value]
        elif hasattr(value, '__dict__'):
            return self._traverse_dict(value.__dict__)
        else: return value
```

次に、このmix-inを使っ2分木の辞書表現を作るクラスを定義し、オブジェクトの属性を出力します。

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

tree = BinaryTree(5,
    left=BinaryTree('aaa', right=BinaryTree(4)),
    right=BinaryTree(2.4, left=BinaryTree(8)))

# treeオブジェクトの属性を辞書にして表示
print(tree.to_dict())
```

mix-inの長所は、特定の型に依存せず、必要な時に機能をオーバーライドできる点です。例えば、先ほどの BinaryTreeのサブクラスを親への参照を保持するようにオーバーライドします。

```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):
        '''
        値の型がBinaryTreeWithParent属性かつ、キーが親クラスだった場合、value.valueを返し、
        それ以外の場合は、親クラスの_traverseと同じ処理をするように変更
        '''
        if (isinstance(value, BinaryTreeWithParent) and
                 key == 'parent'):
            return value.value    # サイクルを防ぐ
        else:
            return super()._traverse(key, value)
```

見やすくするために、pprintで出力する

```python
import pprint

root = BinaryTreeWithParent(5)
root.left = BinaryTreeWithParent(3, parent=root)
root.left.right = BinaryTreeWithParent(13, parent=root.left)
pprint.pprint(root.to_dict())
```

出力結果

```result
{'left': {'left': None,
          'parent': 5,
          'right': {'left': None, 'parent': 3, 'right': None, 'value': 13},
          'value': 3},
 'parent': None,
 'right': None,
 'value': 5}
 ```

BinaryTreeWithParent._traverse を定義することで、BinaryTreeWithParent型の属性を持つすべてのクラスでも、ToDictMixinが自動的に働きます。

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

mytree = NamedSubTree('bbb',root.left.right)
pprint.pprint(mytree.to_dict())
```

出力結果

```result
{'name': 'bbb',
 'tree_with_parent': {'left': None, 'parent': 3, 'right': None, 'value': 13}}
 ```

# まとめ

- 基本的には、多重継承よりmix-inを使う
- mix-inクラスが必要なときに、クラスごとにカスタマイズする