はじめに
UserDict、UserList、UserStringは、辞書・リスト・文字列を継承してカスタマイズするためのラッパークラスです。直接dictやlistを継承するより安全にカスタマイズできます。
なぜ UserDict / UserList を使うのか
dict を直接継承する問題
# dict を直接継承
class MyDict(dict):
def __setitem__(self, key, value):
print(f"Setting {key} = {value}")
super().__setitem__(key, value)
d = MyDict()
d['a'] = 1 # Setting a = 1 ← 動作する
# しかし update() は __setitem__ を呼ばない!
d.update({'b': 2}) # 何も表示されない
print(d) # {'a': 1, 'b': 2}
注意:
dict/listを直接継承すると、一部メソッド(update()など)が CPython の C 実装を直接呼び出すため、オーバーライドが効かない場合があります。これは CPython の内部実装に依存する動作であり、すべてのメソッドでこの問題が起きるわけではありませんが、予測しにくいためUserDict/UserListを使う方が安全です。
UserDict なら安全
from collections import UserDict
class MyDict(UserDict):
def __setitem__(self, key, value):
print(f"Setting {key} = {value}")
super().__setitem__(key, value)
d = MyDict()
d['a'] = 1 # Setting a = 1
d.update({'b': 2}) # Setting b = 2 ← 正しく動作!
UserDict の基本
構造
from collections import UserDict
class MyDict(UserDict):
# self.data で内部の辞書にアクセス
pass
d = MyDict({'a': 1})
print(d.data) # {'a': 1}
基本的なカスタマイズ
from collections import UserDict
class CaseInsensitiveDict(UserDict):
"""キーの大文字小文字を区別しない辞書"""
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
def __getitem__(self, key):
return super().__getitem__(key.lower())
def __contains__(self, key):
return super().__contains__(key.lower())
d = CaseInsensitiveDict()
d['Name'] = 'Tanaka'
print(d['name']) # Tanaka
print(d['NAME']) # Tanaka
print('name' in d) # True
print(d.data) # {'name': 'Tanaka'}(キーは小文字で保存される)
補足: この実装ではキーはすべて小文字に変換して保存されるため、元のキーの表記(
'Name'など)は保持されません。元の表記を保持したい場合は、別途元キーを記録する実装が必要です。
UserDict の実践例
バリデーション付き辞書
from collections import UserDict
class TypedDict(UserDict):
"""型をバリデーションする辞書"""
def __init__(self, key_type, value_type, *args, **kwargs):
self.key_type = key_type
self.value_type = value_type
super().__init__(*args, **kwargs)
def __setitem__(self, key, value):
if not isinstance(key, self.key_type):
raise TypeError(f"Key must be {self.key_type.__name__}")
if not isinstance(value, self.value_type):
raise TypeError(f"Value must be {self.value_type.__name__}")
super().__setitem__(key, value)
# 使用例
d = TypedDict(str, int)
d['age'] = 25 # OK
# d['age'] = 'twenty' # TypeError: Value must be int
# d[123] = 456 # TypeError: Key must be str
履歴付き辞書
from collections import UserDict
from datetime import datetime
class HistoryDict(UserDict):
"""変更履歴を記録する辞書"""
def __init__(self, *args, **kwargs):
self.history = []
super().__init__(*args, **kwargs)
def __setitem__(self, key, value):
old_value = self.data.get(key)
self.history.append({
'timestamp': datetime.now().isoformat(),
'action': 'update' if key in self.data else 'create',
'key': key,
'old_value': old_value,
'new_value': value
})
super().__setitem__(key, value)
def __delitem__(self, key):
self.history.append({
'timestamp': datetime.now().isoformat(),
'action': 'delete',
'key': key,
'old_value': self.data.get(key),
'new_value': None
})
super().__delitem__(key)
# 使用例
d = HistoryDict()
d['name'] = 'Tanaka'
d['name'] = 'Yamada'
del d['name']
for record in d.history:
print(f"{record['action']}: {record['key']} = {record['old_value']} -> {record['new_value']}")
デフォルト値付き辞書(カスタム)
from collections import UserDict
class DefaultDict(UserDict):
"""アクセス時にデフォルト値を返す(存在しないキーは作らない)"""
def __init__(self, default, *args, **kwargs):
self.default = default
super().__init__(*args, **kwargs)
def __getitem__(self, key):
if key in self.data:
return self.data[key]
return self.default
# defaultdictとの違い: キーを自動作成しない
d = DefaultDict(0)
print(d['missing']) # 0
print('missing' in d) # False
UserList の基本
構造
from collections import UserList
class MyList(UserList):
# self.data で内部のリストにアクセス
pass
lst = MyList([1, 2, 3])
print(lst.data) # [1, 2, 3]
基本的なカスタマイズ
from collections import UserList
class PositiveList(UserList):
"""正の数のみを許可するリスト"""
def _validate(self, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError("Only positive numbers allowed")
def append(self, value):
self._validate(value)
super().append(value)
def insert(self, i, value):
self._validate(value)
super().insert(i, value)
def __setitem__(self, i, value):
self._validate(value)
super().__setitem__(i, value)
# 使用例
lst = PositiveList()
lst.append(5)
lst.append(10)
# lst.append(-3) # ValueError
print(lst) # [5, 10]
補足: この例では
append()、insert()、__setitem__()など代表的なメソッドのみをオーバーライドしています。完全な実装では+=(__iadd__)やスライス代入などもカバーする必要がありますが、記事では主要なパターンを示すことを目的としています。
UserList の実践例
ユニークリスト
from collections import UserList
class UniqueList(UserList):
"""重複を許さないリスト"""
def __init__(self, iterable=None):
super().__init__()
if iterable:
for item in iterable:
self.append(item)
def append(self, item):
if item not in self.data:
super().append(item)
def insert(self, i, item):
if item not in self.data:
super().insert(i, item)
def extend(self, other):
for item in other:
self.append(item)
# 使用例
lst = UniqueList([1, 2, 2, 3, 3, 3])
print(lst) # [1, 2, 3]
lst.append(2)
print(lst) # [1, 2, 3](変化なし)
上限付きリスト
from collections import UserList
class BoundedList(UserList):
"""最大長を持つリスト"""
def __init__(self, maxlen, iterable=None):
self.maxlen = maxlen
super().__init__()
if iterable:
for item in iterable:
self.append(item)
def _check_size(self):
while len(self.data) > self.maxlen:
self.data.pop(0)
def append(self, item):
super().append(item)
self._check_size()
def insert(self, i, item):
super().insert(i, item)
self._check_size()
def extend(self, other):
super().extend(other)
self._check_size()
# 使用例
lst = BoundedList(3)
lst.extend([1, 2, 3, 4, 5])
print(lst) # [3, 4, 5]
ソート済みリスト
from collections import UserList
import bisect
class SortedList(UserList):
"""常にソートされた状態を維持するリスト"""
def __init__(self, iterable=None):
super().__init__()
if iterable:
for item in iterable:
self.add(item)
def add(self, item):
"""ソート順を維持して追加"""
bisect.insort(self.data, item)
def append(self, item):
self.add(item)
def insert(self, i, item):
self.add(item)
def extend(self, other):
for item in other:
self.add(item)
# 使用例
lst = SortedList([3, 1, 4, 1, 5])
print(lst) # [1, 1, 3, 4, 5]
lst.add(2)
print(lst) # [1, 1, 2, 3, 4, 5]
UserString の基本
from collections import UserString
class ReversibleString(UserString):
"""反転メソッドを持つ文字列"""
def reversed(self):
return ReversibleString(self.data[::-1])
s = ReversibleString("hello")
print(s.reversed()) # olleh
print(s.upper()) # HELLO(通常のメソッドも使える)
まとめ
| クラス | 内部データ | 用途 |
|---|---|---|
| UserDict |
self.data (dict) |
カスタム辞書 |
| UserList |
self.data (list) |
カスタムリスト |
| UserString |
self.data (str) |
カスタム文字列 |
使い分け
-
UserDict/UserList/UserString を使う場合:
- メソッドをオーバーライドしたい
- 安全なカスタマイズが必要
- 既存の操作を拡張したい
-
直接継承を使う場合:
- パフォーマンスが重要
- 内部実装を理解している







