いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Pythonメモ-35 (基本コンテナオブジェクトと読み取り専用オブジェクト) (list, dict, set, tuple, MappingProxyType, frozenset)

概要

python には、基本的なコンテナとして

  • list
  • dict
  • set

が用意されています。で、他の言語(特にコンパイル系の言語)

やってた人がよく言ってくるのが

「読み取り専用のコレクションみたいなのないの? IReadOnlyCollectionみたいな」

って質問です。python の場合、明確にlistに対してはコレっていうように

決まった型があるわけではないのですが、同じような効果を持つオブジェクトが

いたりします。

  • list に対しては tuple
  • dict に対しては types.MappingProxyType
  • set に対しては frozenset

って感じです。

types.MappingProxyTypeだけ、ちょっと特殊ですね。

こいつは、動的ビューなイメージを持つクラスで、元データの変更は即座に反映するけど

データの追加などはサポートしていないっていう型です。

サンプル

以下、サンプルです。

"""
基本コンテナオブジェクトの読み取り専用モードオブジェクトについてのサンプルです。
"""
import types

from trypython.common.commoncls import SampleBase
from trypython.common.commonfunc import pr, hr


class Sample(SampleBase):
    def exec(self):
        ###################################################################
        #
        # 基本コンテナオブジェクトと読み取り専用オブジェクト
        #
        # Python の基本コンテナとして
        #   * リスト (list)
        #   * ディクショナリ (dict)
        #   * セット (set)
        # が存在するが、それぞれ読み取り専用として扱えるオブジェクトが
        # 対で存在する。対応としては以下のようになる。
        #
        # リスト
        #    tuple に変更すると読み取り専用のリストとして使える
        # ディクショナリ
        #    types.MappingProxyType オブジェクトが読み取り専用として使える
        # セット
        #    frozenset が読み取り専用として使える
        #
        ##################################################################
        #
        # listとtuple
        #
        a_list = list(range(10))
        pr('list', a_list)

        a_list.append(100)
        a_list.insert(0, 101)
        del a_list[1]

        pr('list is mutable', a_list)

        a_tuple = tuple(a_list)
        pr('tuple', a_tuple)

        try:
            # noinspection PyUnresolvedReferences
            a_tuple.append(102)
        except AttributeError as attr_ex:
            pr('tuple is immutable', attr_ex)

        try:
            # noinspection PyUnresolvedReferences
            del a_tuple[0]
        except TypeError as type_ex:
            pr('tuple is immutable2', type_ex)

        # tupleは生成時に元データをコピーして生成されるので
        # 元のデータに変更があっても影響はない
        a_list.clear()
        a_list.append(999)
        pr('tuple', a_tuple)

        hr()

        #
        # dictとtypes.MappingProxyType
        #
        a_dict = dict(hello=1, world=2)
        pr('dict', a_dict)

        a_dict['spam'] = 100
        del a_dict['world']

        pr('dict is mutable', a_dict)

        a_proxy = types.MappingProxyType(a_dict)
        try:
            # noinspection PyUnresolvedReferences
            a_proxy['hello'] = 200
        except TypeError as type_ex:
            pr('MappingProxyType is immutable', type_ex)

        try:
            # noinspection PyUnresolvedReferences
            del a_proxy['hello']
        except TypeError as type_ex:
            pr('MappingProxyType is immutable2', type_ex)

        # このオブジェクト自体が動的ビューなので
        # 元オブジェクトの変更は即座に反映される。
        # (tupleやfrozensetの場合は元の値がコピーされているため元データ
        #  変更しても影響はない。)
        a_dict['world'] = 999
        pr('proxy', a_proxy['world'])

        hr()

        #
        # setとfrozenset
        #
        a_set = set(a_tuple)
        pr('set', a_set)

        a_set.add(10)
        a_set.add(3)
        a_set.remove(2)

        pr('set is mutable', a_set)

        a_frozenset = frozenset(a_set)

        try:
            # noinspection PyUnresolvedReferences
            a_frozenset.add(4)
        except AttributeError as attr_ex:
            pr('frozenset is immutable', attr_ex)

        try:
            # noinspection PyUnresolvedReferences
            a_frozenset.remove(10)
        except AttributeError as attr_ex:
            pr('frozenset is immutable2', attr_ex)

        # frozensetは生成時に元データをコピーして生成されるので
        # 元のデータに変更があっても影響はない
        a_set.clear()
        a_set.add(999)
        pr('frozenset', a_frozenset)

        hr()


def go():
    obj = Sample()
    obj.exec()


if __name__ == '__main__':
    go()

実行結果は以下のようになります。

list=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list is mutable=[101, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]
tuple=(101, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100)
tuple is immutable=AttributeError("'tuple' object has no attribute 'append'",)
tuple is immutable2=TypeError("'tuple' object doesn't support item deletion",)
tuple=(101, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100)
--------------------------------
dict={'hello': 1, 'world': 2}
dict is mutable={'hello': 1, 'spam': 100}
MappingProxyType is immutable=TypeError("'mappingproxy' object does not support item assignment",)
MappingProxyType is immutable2=TypeError("'mappingproxy' object does not support item deletion",)
proxy=999
--------------------------------
set={1, 2, 3, 4, 101, 5, 6, 7, 8, 9, 100}
set is mutable={1, 3, 4, 101, 5, 6, 7, 8, 9, 100, 10}
frozenset is immutable=AttributeError("'frozenset' object has no attribute 'add'",)
frozenset is immutable2=AttributeError("'frozenset' object has no attribute 'remove'",)
frozenset=frozenset({1, 3, 4, 101, 5, 6, 7, 8, 9, 100, 10})
--------------------------------

参考情報

Fluent Python ―Pythonicな思考とコーディング手法

Fluent Python ―Pythonicな思考とコーディング手法

  • 作者: Luciano Ramalho,豊沢聡,桑井博之,梶原玲子
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2017/10/07
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

types.MappingProxyTypeは、この本から知りました。

5. データ構造 — Python 3.6.3 ドキュメント

8.9. types — 動的な型生成と組み込み型に対する名前 — Python 3.6.3 ドキュメント

4. 組み込み型 — Python 3.6.3 ドキュメント

補足

後からここ見て知りましたが

stackoverflow.com

dictの場合はnamedtupleでもいいかもしれないですね。

a_dict = dict(hello=1, world=2)
MyFrozenDict = collections.namedtuple('MyFrozenDict', a_dict.keys())

a_frozendict = MyFrozenDict(**a_dict)

print(a_frozendict.hello)

a_frozendict.hello = 100 # raise AttributeError

まあ、アクセス方法がちょっと変わってしまうので、なんとも言えないですが。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。