Pythonで読み取り専用コレクションを実装する
今回は、Pythonで読み取り専用コレクションのメンバ変数を実装する方法を解説します。
概要
通常、読み取り専用のメンバ変数を実装するときは、メンバ変数をプライベートにしつつ、getterのみ宣言して、setterを宣言しないようにします。
class Name: def __init__(self): self._firstname = 'Fugataro' @property def firstname(self): return self._firstname name = Name() print(name.firstname) name.firstname = 'Hogemi' # Fugataro # AttributeError: can't set attribute
注意が必要なのは、list
やdict
などのコレクション型変数をメンバ変数とする場合です。
上記と同様に、getterのみ宣言して、setterを宣言しないことにより、コレクション型のメンバ変数を別のコレクション型変数に置き換えることは出来ません。
class NumbersList: def __init__(self): self._data = [1, 2, 3] @property def data(self): return self._data numbers = NumbersList() print(numbers.data) numbers.data = [4, 5, 6] # [1, 2, 3] # AttributeError: can't set attribute
しかし、append()
やremove()
などでコレクション型メンバ変数の中身を書き換えることが出来てしまっています。これは、getterのみの宣言により、メンバ変数そのものの置き換えは出来ないようになりましたが、コレクション型メンバ変数の関数はいまだ機能しているため、結果としてそれら関数により、中身の書き換えが出来てしまっているのです。
class NumbersList: def __init__(self): self._data = [1, 2, 3] @property def data(self): return self._data numbers = NumbersList() print(numbers.data) numbers.data.append(4) numbers.data.extend([5, 6]) numbers.data.remove(1) print(numbers.data) # [1, 2, 3] # [2, 3, 4, 5, 6]
通常、この書き換えを防ぐために、別途用意されている読み取り専用のコレクションクラスを利用します。
Pythonの場合、読み取り専用のコレクションクラスは用意されていません。しかし、基本的なコレクションであるlist
、dict
、set
に対して、以下を用いることにより、代用可能です。
通常 | 読み取り専用 |
---|---|
list | tuple |
dict | types.MappingProxyType |
set | frozenset |
実装方法
list
、dict
、set
に対して、対応する代用のコレクションにてキャストすればよいです。以下にサンプルソースを記載します。
tuple
class ReadOnlyList: def __init__(self): self._data = tuple([1, 2, 3]) @property def data(self): return self._data l = ReadOnlyList() print(l.data) print(l.data[0]) print(l.data[1]) print(l.data[2]) for i in l.data: print(i) l.data.append(4) # (1, 2, 3) # 1 # 2 # 3 # 1 # 2 # 3 # AttributeError: 'tuple' object has no attribute 'append'
types.MappingProxyType
import types class ReadOnlyDict: def __init__(self): d = {'msg': 'Hello,World!', 'param': 1} self._data = types.MappingProxyType(d) @property def data(self): return self._data d = ReadOnlyDict() print(d) print(f'msg: {d.data["msg"]}') print(f'param: {d.data["param"]}') for key in d.data: print(key) for value in d.data.values(): print(value) for key, value in d.data.items(): print(key, value) d.data['name'] = 'Foge' # <__main__.ReadOnlyDict object at 0x0000024B239D7D88> # msg: Hello,World! # param: 1 # msg # param # Hello,World! # 1 # msg Hello,World! # param 1 # TypeError: 'mappingproxy' object does not support item assignment
frozenset
class ReadOnlySet: def __init__(self): s = set((1, 2, 3)) self._data = frozenset(s) @property def data(self): return self._data s = ReadOnlySet() print(s.data) for i in s.data: print(i) s.data.add(4) # frozenset({1, 2, 3}) # 1 # 2 # 3 # AttributeError: 'frozenset' object has no attribute 'add'