Python 3.7以降では、辞書(dict)が挿入順序を保持することが公式に保証されています。この記事では、バージョンごとの違いと実際の使用例を解説します。
Pythonバージョンごとの辞書の順序
| バージョン | 順序保持 | 保証 | 備考 |
|---|---|---|---|
| Python 3.5以前 | × | - | 順序は不定 |
| Python 3.6 | ○ | 実装依存 | CPythonのみ |
| Python 3.7以降 | ○ | 公式保証 | 全実装で保証 |
辞書の順序保持の確認
# Python 3.7以降で動作確認
d = {}
d['first'] = 1
d['second'] = 2
d['third'] = 3
d['fourth'] = 4
print(list(d.keys())) # ['first', 'second', 'third', 'fourth']
print(list(d.values())) # [1, 2, 3, 4]
print(list(d.items())) # [('first', 1), ('second', 2), ('third', 3), ('fourth', 4)]
# forループでの順序も保持される
for key, value in d.items():
print(f"{key}: {value}")
# first: 1
# second: 2
# third: 3
# fourth: 4
辞書リテラルでも順序が保持される
# 辞書リテラルの記述順序が保持される
config = {
'host': 'localhost',
'port': 8080,
'debug': True,
'timeout': 30
}
for key in config:
print(key)
# host
# port
# debug
# timeout
dict vs OrderedDict
Python 3.7以降でもOrderedDictには固有の機能があります。
比較
| 機能 | dict | OrderedDict |
|---|---|---|
| 挿入順序の保持 | ○ | ○ |
| 順序を考慮した等価比較 | × | ○ |
move_to_end() | × | ○ |
popitem(last=False) | △(3.7+でlast=Trueのみ) | ○ |
| メモリ効率 | ○ | × |
等価比較の違い
from collections import OrderedDict
# 通常のdictは順序を無視して比較
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True(順序は無視)
# OrderedDictは順序も含めて比較
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False(順序が異なる)
# dictとOrderedDictの比較は順序を無視
print(d1 == od1) # True
move_to_end()の活用
from collections import OrderedDict
# LRUキャッシュの簡易実装
class SimpleLRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key):
if key in self.cache:
# アクセスされたら末尾に移動
self.cache.move_to_end(key)
return self.cache[key]
return None
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
# 最も古い項目を削除
self.cache.popitem(last=False)
# 使用例
cache = SimpleLRUCache(3)
cache.put('a', 1)
cache.put('b', 2)
cache.put('c', 3)
print(list(cache.cache.keys())) # ['a', 'b', 'c']
cache.get('a') # 'a'にアクセス
print(list(cache.cache.keys())) # ['b', 'c', 'a']
cache.put('d', 4) # 容量超過で'b'が削除
print(list(cache.cache.keys())) # ['c', 'a', 'd']
実践的な例
設定ファイルの順序保持
import json
# 設定を定義順で保持
config = {
'app_name': 'MyApp',
'version': '1.0.0',
'database': {
'host': 'localhost',
'port': 5432,
'name': 'mydb'
},
'logging': {
'level': 'INFO',
'file': 'app.log'
}
}
# JSONに出力しても順序が保持される
print(json.dumps(config, indent=2, ensure_ascii=False))
データの順序付きグルーピング
from collections import defaultdict
data = [
('2024-01', 100),
('2024-02', 150),
('2024-03', 120),
('2024-01', 80),
('2024-02', 200),
]
# グループ化(挿入順序が保持される)
grouped = defaultdict(list)
for month, value in data:
grouped[month].append(value)
# 最初に出現した順序で出力
for month, values in grouped.items():
print(f"{month}: {values}")
# 2024-01: [100, 80]
# 2024-02: [150, 200]
# 2024-03: [120]
キーの並び替え
# 辞書のキーをソートして新しい辞書を作成
original = {'c': 3, 'a': 1, 'b': 2}
# キーでソート
sorted_by_key = dict(sorted(original.items()))
print(sorted_by_key) # {'a': 1, 'b': 2, 'c': 3}
# 値でソート
sorted_by_value = dict(sorted(original.items(), key=lambda x: x[1]))
print(sorted_by_value) # {'a': 1, 'b': 2, 'c': 3}
# 値で降順ソート
sorted_desc = dict(sorted(original.items(), key=lambda x: x[1], reverse=True))
print(sorted_desc) # {'c': 3, 'b': 2, 'a': 1}
順序を保持した辞書の更新
# 既存のキーを更新しても順序は変わらない
d = {'a': 1, 'b': 2, 'c': 3}
d['a'] = 100 # 既存キーの更新
print(list(d.keys())) # ['a', 'b', 'c'](順序は変わらない)
# 削除して追加すると末尾に移動
del d['a']
d['a'] = 1
print(list(d.keys())) # ['b', 'c', 'a']('a'が末尾に)
Python 3.5以前との互換性
古いバージョンとの互換性が必要な場合の対応方法です。
import sys
from collections import OrderedDict
def create_ordered_dict(*args, **kwargs):
"""バージョンに応じて適切な辞書を返す"""
if sys.version_info >= (3, 7):
return dict(*args, **kwargs)
else:
return OrderedDict(*args, **kwargs)
# Python 3.6以前でも順序を保証したい場合
config = create_ordered_dict([
('host', 'localhost'),
('port', 8080),
('debug', True)
])
辞書の内部実装
Python 3.6以降の辞書は、コンパクトなハッシュテーブルを使用しています。
# 辞書の内部構造を確認
import sys
d = {'a': 1, 'b': 2, 'c': 3}
# メモリサイズの確認
print(f"dict size: {sys.getsizeof(d)} bytes")
# 同じデータをOrderedDictで
from collections import OrderedDict
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
print(f"OrderedDict size: {sys.getsizeof(od)} bytes")
# OrderedDictの方がメモリ使用量が多い
よくある誤解と注意点
キーの型と順序
# 異なる型のキーでも挿入順序は保持
d = {}
d[1] = 'one'
d['a'] = 'alpha'
d[(1, 2)] = 'tuple'
d[3.14] = 'pi'
print(list(d.keys())) # [1, 'a', (1, 2), 3.14]
辞書のコピーと順序
original = {'c': 3, 'a': 1, 'b': 2}
# 各種コピー方法で順序は保持される
copy1 = original.copy()
copy2 = dict(original)
copy3 = {**original}
print(list(copy1.keys())) # ['c', 'a', 'b']
print(list(copy2.keys())) # ['c', 'a', 'b']
print(list(copy3.keys())) # ['c', 'a', 'b']
JSON読み込み時の順序
import json
json_str = '{"c": 3, "a": 1, "b": 2}'
# Python 3.7以降では順序が保持される
data = json.loads(json_str)
print(list(data.keys())) # ['c', 'a', 'b']
# 明示的にOrderedDictを使う場合
from collections import OrderedDict
data_ordered = json.loads(json_str, object_pairs_hook=OrderedDict)
まとめ
| 状況 | 推奨 |
|---|---|
| Python 3.7以降で順序が必要 | 通常のdictを使用 |
| 順序を考慮した等価比較が必要 | OrderedDictを使用 |
move_to_end()が必要 | OrderedDictを使用 |
| Python 3.6以前との互換性 | OrderedDictを使用 |
| メモリ効率重視 | 通常のdictを使用 |
Python 3.7以降では、通常のdictが挿入順序を保持することが公式に保証されているため、ほとんどの場合はdictで十分です。特別な機能が必要な場合のみOrderedDictを使用しましょう。