Python プログラミングにおいて、辞書は非常に強力なデータ構造であり、キーと値のペアを関連付け、効率的にデータを検索および操作することを可能にします。カスタムオブジェクトを辞書に格納しようとすると、通常、重要な概念である「Python におけるオブジェクト参照(参照渡し)」が問題となります。つまり、カスタムオブジェクトを辞書に入れる場合、辞書はオブジェクトへの参照を格納するだけであり、オブジェクトの完全なコピーを作成しているわけではありません。
カスタムオブジェクトの基本的な例
以下の簡単な Person
クラスを想定してください:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# Person オブジェクトを作成
p1 = Person("Alice", 30)
# オブジェクトを辞書に保存
people_dict = {}
people_dict["alice"] = p1
この例では、people_dict
辞書が "alice"
というキーを持つ項目を含み、その値は Person
型の p1
オブジェクトへの参照です。 p1
の属性を変更すると:
p1.age = 31
辞書からオブジェクトにアクセスするときに、その年齢も更新されていることに気づきます:
print(people_dict["alice"].age) # 出力: 31
これは、辞書が Person
オブジェクトの独立したコピーを保存するのではなく、同じメモリ位置への参照を保存しているためです。
深復元と浅復元の違い
ネストされたデータ構造やカスタムオブジェクトを扱う場合、このような参照の動作は予期せぬ結果を引き起こす可能性があります。例えば、カスタムオブジェクトが可変型の属性(リストや別のカスタムオブジェクトなど)を含む場合、そのようなオブジェクトを辞書に直接格納し、その変更を加えると、辞書から取得したオブジェクトも影響を受けます。
class Address:
def __init__(self, street, city):
self.street = street
self.city = city
class Person:
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
address = Address("Main St.", "Springfield")
p1 = Person("Bob", 40, address)
people_dict["bob"] = p1
# 原始アドレスオブジェクトを変更
address.city = "Shelbyville"
# 辞書内の人のアドレスも変更される
print(people_dict["bob"].address.city) # 出力:Shelbyville
解決策:深復元
このような共有状態の問題を回避するためには、辞書がオブジェクトの完全なコピーを格納していることを確認する必要があります。つまり、参照ではなく、独立したコピーである必要があります。Python は copy
モジュールにある deepcopy
関数を使用してこれを実現します。
import copy
# オブジェクトを深復元で格納
people_dict["bob_deepcopy"] = copy.deepcopy(p1)
# 原始アドレスオブジェクトを変更しても、深復元されたオブジェクトには影響しない
address.city = "Capital City"
print(people_dict["bob"].address.city) # 出力:Capital City
print(people_dict["bob_deepcopy"].address.city) # 出力:Capital City
要するに、Python で辞書を使用してカスタムオブジェクトを格納する場合は、デフォルトでオブジェクトの参照が格納されることに注意してください。独立した状態を維持する必要がある場合は、deepcopy
を使用して深復元を行い、共有参照による予期せぬデータ変更を防ぐようにしてください。