`types.MappingProxyType`をシミュレートする

# 不変な辞書って欲しいよね
なにがしかのデータを辞書形式で集めた後、それを参照するだけの用途で用いるということをしたいときに、「うっかり」新たな`key`を追加してしまったり、`value`を変えてしまったりしないように保証した辞書が欲しい。

# 通常は`types.MappingProxyType`を使う
```python
from types import MappingProxyType

assc_arr = {
    'foo': 'hoge',
    'bar': 'fuga',
    'baz': 'piyo'
}

frozen_assoc_arr = MappingProxyType(assoc_arr)
frozen_assoc_arr['qux'] = 'moge'
# >>> TypeError: 'mappingproxy' object does not support item assignment

```

# じゃあ、サブクラス化したいときは?
可能かどうかを試すために、`MappingProxyType`をただ継承元にしたクラスを作ると、

```python

class FrozenMapping(MappingProxyType):
    pass
# >>> TypeError: type 'mappingproxy' is not an acceptable base type
```
基底クラスにできないと怒られてしまう。

# `collections.UserDict`を参考にしよう
標準ライブラリ内のクラス[collections.UserDict](https://docs.python.org/ja/3.7/library/collections.html?highlight=userdict#userdict-objects)は

>辞書をシミュレートするクラスです。インスタンスの内容は通常の辞書に保存され、 UserDict インスタンスの data 属性を通してアクセスできます。 initialdata が与えられれば、 data はその内容で初期化されます。他の目的のために使えるように、 initialdata への参照が保存されないことがあるということに注意してください。

というように、内部に`data`属性として`dict`インスタンスを持っていて、これに`UserDict`自身に実装されたメソッドを介してアクセスして辞書のような振る舞いをしている。

# でも欲しいのってImmutableな辞書
`UserDict`は既に`__setitem__`や`__delitem__`を実装してしまっているので、これをサブクラス化することでImmutableな辞書を実装することはできない。

# 基底クラスを辿る旅
`UserDict`は抽象クラス`_collections_abc.MutableMapping`を継承している。
`_collections_abc.MutableMapping`はさらに`_collections_abc.Mapping`を継承している。

```python:_collections_abc.py

class Mapping(Collection):

    __slots__ = ()

    """A Mapping is a generic container for associating key/value
    pairs.

    This class provides concrete generic implementations of all
    methods except for __getitem__, __iter__, and __len__.

    """

    @abstractmethod
    def __getitem__(self, key):
        raise KeyError

    def get(self, key, default=None):
        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        try:
            self[key]
        except KeyError:
            return False
        else:
            return True

    def keys(self):
        "D.keys() -> a set-like object providing a view on D's keys"
        return KeysView(self)

    def items(self):
        "D.items() -> a set-like object providing a view on D's items"
        return ItemsView(self)

    def values(self):
        "D.values() -> an object providing a view on D's values"
        return ValuesView(self)

    def __eq__(self, other):
        if not isinstance(other, Mapping):
            return NotImplemented
        return dict(self.items()) == dict(other.items())

    __reversed__ = None
```
これから継承していくのがよさそうだ。

# 実装
```python

from _collections_abc import Mapping


class ImmutableMapping(Mapping):
    def __init__(self, data=None):
        self._data = data if data is not None else {}

    def __len__(self):
        return len(self._data)

    def __getitem__(self, key):
        if key in self._data:
            return self._data[key]
        if hasattr(self.__class__, '__missing__'):
            return self.__class__.__missing__(self, key)
        raise KeyError(key)

    def __iter__(self):
        return iter(self._data)

    def __contains__(self, key):
        return key in self._data

    def __repr__(self):
        return f'ImmutableMapping({repr(self._data)})'

    @classmethod
    def recursively(cls, data={}):
        d = {}
        for key, val in data.items():
            if isinstance(data, (dict, Mapping)):
                val = cls(val)
            d[key] = val
        return cls(d)

```

# サブクラス化の例
```python
class SampleFrozenMapping(ImmutableMapping):
    def __init__(self, name):
        data = {
            f'{name}`s parrot': 'Polly'
        }
        ImmutableMapping.__init__(self, data)


if __name__ == '__main__':
    foo = SampleFrozenMapping('Mr. Praline')
    print(foo['Mr. Praline`s parrot'])
    # >>> Polly
```

# `ImmutableMapping.recursively`って?
こういう機能は`MappingProxyType`には存在しないが、必要な場面があるために実装した。
引数に渡した辞書の`value`が`dict`または`_collections_abc.Mapping`のサブクラスであれば、それも`ImmutableMapping`に変換する。

```python
if __name__ == '__main__':
    foo = {
        'a': {
            'hoge': 'spam',
            'fuga': 'ham'
        },
        'b': {
            'moge': 'egg',
            'piyo': 'bacon'
        }
    }

    bar = ImmutableMapping.recursively(foo)
    # >>> ImmutableMapping({
    #         'a': ImmutableMapping({'hoge': 'spam', 'fuga': 'ham'}),
    #         'b': ImmutableMapping({'moge': 'egg', 'piyo': 'bacon'})
    #     })
```

# 展望
immutableな`collections.defaultdict`も実装したい。