わかってしまえば当たり前なのですが、ちょっとハマったのでメモ。
結論から先に言うと、clear()
update()
を使うことで実現できました(ただし排他には要注意)。
環境
- Python 3.8.10
前提知識
以下の話はググれば解説されているページがいくらでも見つかりますが・・・。
dict
はミュータブルな型なので、代入する場合には注意が必要です。
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
dict_a = dict_b # 代入
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
代入した場合には同じオブジェクトを指すようになるので、以下のように id
が同じになります。
dict_a={'key_a': 'value_a'}, id:140351608696704
dict_b={'key_b': 'value_b'}, id:140351608696768
----------
dict_a={'key_b': 'value_b'}, id:140351608696768
dict_b={'key_b': 'value_b'}, id:140351608696768
なので、dic_a
を変更したら dict_b
の中身も変わります。
これを回避するには(つまり id
は別で中身だけをコピーしたい場合は) copy()
を使います。
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
dict_a = dict_b.copy()
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
この場合は新たにオブジェクトが作成されるので、id
は別で中身は同じになります。
dict_a={'key_a': 'value_a'}, id:139764478058368
dict_b={'key_b': 'value_b'}, id:139764478058432
----------
dict_a={'key_b': 'value_b'}, id:139764476783232
dict_b={'key_b': 'value_b'}, id:139764478058432
じゃあ id を変えずに中身だけをコピーするには?
clear()
と update()
を使えばできます。
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
dict_a.clear()
dict_a.update(dict_b)
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
dict_a={'key_a': 'value_a'}, id:139790269009792
dict_b={'key_b': 'value_b'}, id:139790269009856
----------
dict_a={'key_b': 'value_b'}, id:139790269009792
dict_b={'key_b': 'value_b'}, id:139790269009856
dict_a
dict_b
の中身は同じですが、dict_a
の id
は変わっていません。
関数に引数で渡す場合
Python では全て参照渡しなので、dict
を関数に渡して関数内で変更した場合、その関数の外にも影響を与えます。
def change_dict(target_dict):
target_dict['hoge'] = 'piyo'
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
change_dict(dict_a)
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
dict_a={'key_a': 'value_a'}, id:139844338024320
dict_b={'key_b': 'value_b'}, id:139844338024384
----------
dict_a={'key_a': 'value_a', 'hoge': 'piyo'}, id:139844338024320
dict_b={'key_b': 'value_b'}, id:139844338024384
本題
じゃあ、関数内で dict
を丸ごと差し替えたい場合はどうすればよいか?
引数で渡した値に丸ごと新しい dict
を代入すると、その引数の参照先が変わるだけなので、参照元には反映されません。
def change_dict(target_dict):
target_dict = {'hoge': 'piyo'}
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
change_dict(dict_a)
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
dict_a={'key_a': 'value_a'}, id:139712684732288
dict_b={'key_b': 'value_b'}, id:139712684732352
----------
dict_a={'key_a': 'value_a'}, id:139712684732288
dict_b={'key_b': 'value_b'}, id:139712684732352
かと言って、copy()
を使うと新たにオブジェクトが作成されるので、この場合も参照元には反映されません(コピー前のオブジェクトを参照しているため)。
def change_dict(target_dict):
target_dict = {'hoge': 'piyo'}.copy()
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
change_dict(dict_a)
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
dict_a={'key_a': 'value_a'}, id:2642357453888
dict_b={'key_b': 'value_b'}, id:2642357181760
----------
dict_a={'key_a': 'value_a'}, id:2642357453888
dict_b={'key_b': 'value_b'}, id:2642357181760
なので、参照元にも変更を反映したい場合には、前述したように clear()
update()
を使います。
def change_dict(target_dict):
target_dict.clear()
target_dict.update({'hoge': 'piyo'})
dict_a = {"key_a": "value_a"}
dict_b = {"key_b": "value_b"}
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
print(f'----------')
change_dict(dict_a)
# 値と id の確認
print(f'dict_a={dict_a}, id:{id(dict_a)}')
print(f'dict_b={dict_b}, id:{id(dict_b)}')
dict_a={'key_a': 'value_a'}, id:2371211616000
dict_b={'key_b': 'value_b'}, id:2371211612864
----------
dict_a={'hoge': 'piyo'}, id:2371211616000
dict_b={'key_b': 'value_b'}, id:2371211612864
排他には注意
clear()
update()
という2つの関数を使って dict
を更新するので、並列処理を行っている場合には排他が必要です。clear()
と update()
の間で値が読み込まれるということが発生しうるからです。
私の場合、マルチプロセスで動いているアプリで実際にそれが発生しました。
(使っていたのは普通の dict
ではなく multiprocessing.Manager().dict()
ですが)