shinobe179の日記

@shinobe179 の技術メモ・やらかし録

【Python】リストや辞書を複製するときは copy() しなきゃいけないし、それらに値として更にリストや辞書が含まれているなら copy.deepcopy() しましょう(自戒

はじめに

はまったのでメモします。

リストや辞書を代入で複製しようとするとハマる

a_dict の構造をそのまま b_dict でも流用して、値だけ b に変えたれ~と思ったとき、代入によってそれをしようとすると、 a_dict['id'] まで b に変わってしまいます。調べるに、どうやらこれが参照渡しというやつらしい。基本情報で出てきた(落ちたけど

>>> a_dict = {'id': 'a'}
>>> a_dict
{'id': 'a'}
>>> b_dict = a_dict
>>> b_dict
{'id': 'a'}
>>> b_dict['id'] = 'b'
>>> b_dict
{'id': 'b'}
>>> a_dict
{'id': 'b'} # ここは a のままでいてほしい
>>>

解決策: copy() を使う

copy() を使うことで、参照渡しでなく値渡しをしましょう。

>>> a_dict = {'id': 'a'}
>>> a_dict
{'id': 'a'}
>>> b_dict = a_dict.copy()
>>> b_dict
{'id': 'a'}
>>> b_dict['id'] = 'b'
>>> b_dict
{'id': 'b'}
>>> a_dict
{'id': 'a'}
>>>

copy() で複製しようとしたリストや辞書の値に、リストや辞書が含まれているとハマる

今度は a_dict['pattern'] に配列を加えてから、 copy() で b_dict を作る。で、配列の内容を b っぽいものに変更すると、 a_dict['pattern'] も b っぽい内容に……なったはずなんだけど、ならない。 Python のバージョンが上がって(以下の REPL は 3.8.1) 、挙動が変わったのかな?

>>> a_dict = {'id': 'a', 'pattern': ['a', 'A']}
>>> a_dict
{'id': 'a', 'pattern': ['a', 'A']}
>>> b_dict = a_dict.copy()
>>> b_dict
{'id': 'a', 'pattern': ['a', 'A']}
>>> b_dict['id'] = 'b'
>>> b_dict['pattern'] = ['b', 'B']
>>> b_dict
{'id': 'b', 'pattern': ['b', 'B']}
>>> a_dict
{'id': 'a', 'pattern': ['a', 'A']} # a_dict['pattern'] も ['b', 'B'] になっちゃってた気がするんだが

……まあいいや、なったとして。それはガワの辞書は copy() によって値渡しできてるけど、中身のリストや辞書は参照渡しされているからみたいです。 Python のリファレンス中ではそれぞれ「浅いコピー」「深いコピー」と呼ばれていて、今回みたいにリストや辞書(クラスなども)が入れ子になっているオブジェクトを「複合オブジェクト」と呼ばれています。

docs.python.org

解決策: copy.deepcopy() を使う

import copy して、 b_dict = copy.deepcopy(a_dict) すると解消します。

...

コピーって、本当に奥が深いですネ!

shinobe179拝