3回に別れてしまったまとめ記事ですがこの1、2の続きです。
と、言う訳で、Mutableなデータを変数で参照した事によって起きる不具合を避けるためにはどうすれば良いか。
回避策:listの場合
回避する方法はデータをコピーする事です。
ちなみに、コピーと言うのは広辞苑無料検索で調べると、「写し、複写」です。
同じデータ内容を持つものをもう別に作る事ですね。
では、どうすればコピーできるのでしょうか。
下記のコードで良いのでしょうか?
>>> p = [1, 2, 3] >>> m = p
何となく”=”は同じものを結ぶイメージがあるので、このコードで、コピーを作る事ができる気がします。
しかし皆さんお気付きと思いますが、実はこれはPythonの変数について1で説明したコードです。
これではコピーできないんです。
みなさん最初にPythonに触れた時の事を思い出してください。
Pythonにおける”=”はただ左の変数に右のデータを代入(assign)するだけです。
変数にデータをassignする、と言う事は参照先が同じ矢印を設定するだけ、「コピーする」の意味を持たないのです。
この感覚を頭に叩き込んでおきたい。
なんだかしつこくて申し訳ない。
そろそろ本気のコピーを見せてやんよ!
という事で、広辞苑の定義通りの効果を持つ本気のコピーはこちら。
>>> p = [1, 2, 3] >>> m = p[:] #本気コピー ただしlistの場合。
m = p[ : ](←p[ : ]はリストpの全要素を切り出す(slice)するコード)
で同じ要素[1, 2, 3]を全く別のメモリ領域に持つmが作られます。続きを含めた一連のコードはこちら。
>>> p = [1, 2, 3] >>> m = p[:] >>> p [1, 2, 3] >>> m [1, 2, 3] >>> p[2] = “sample" >>> p [1, 2, 'sample'] >>> m [1, 2, 3]
最後のmを見てわかる通り、pで参照しているリストの要素を変更しても、コピーした同じ要素を持つ別の領域にあるリストを参照しているmには影響ははありません。
リストの場合はこれで一件落着。
回避策:listに限らない方法
しかし、このsliceでコピーする方法は同じMutableな形式のものでもdictionaryやsetでは使えません。
と、言うのもこの二つ(dictionary, set)はlistの様に順番通り(sequential)にデータを持っている形式ではないからです。
その様な場合には標準ライブラリ(Standard library)のcopyメソッドがあるから大丈夫。
このメソッドを使用する事でdictionaryもsetもlistもコピーできます。
下記はdictionaryの例。
>>> d1 = {"fish":1, "pig":2, "beef":4} >>> d2 = d1.copy() #本気コピー listに限らない奴 >>> d1 {'fish': 1, 'pig': 2, 'beef': 4} >>> d2 {'fish': 1, 'pig': 2, 'beef': 4} >>> d2["beef"] = 100 >>> d2 {'fish': 1, 'pig': 2, 'beef': 100} >>> d1 {'fish': 1, 'pig': 2, 'beef': 4}
ご覧の通り、d2はd1のデータを違う領域にコピーしたデータなので、d1を変えても影響を受けません。
蛇足ですが
このcopyメソッド、今回紹介した浅い(shallow)コピーができる標準ライブラリのcopyメソッドと、copyモジュールをインポートする事で使える深い(deep)コピーがあります。
しかし、ひとまずは今回の浅いコピーで十分と思います。
というのも、深いコピーというのはこのdictionaryやlistが他の外部のオブジェクトを含んでいた場合にそのオブジェクトまでコピーできるコピーだそうです。
Pythonチュートリアル原文ママ
浅い (shallow) コピーと深い (deep) コピーの違いが関係するのは、複合オブジェクト (リストやクラスインスタンスのような他のオブジェクトを含むオブジェクト) だけです:
深いコピー (deep copy) は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入します。
我ながら、この文を読んで意味を実感できる様になってからの使用が良さそうです。
深いコピーは再帰エラーなど起こす可能性があるという事なので、ひとまず使わない報告で問題ないです。浅いも深いもみんなコピーなんだから。
以上、蛇足終わり。
何が言いたかったのか
理解しておきたいのは、Mutableなデータを変更した時、”=”で同じ参照先にしている別の変数のデータも変わってしまう、という事について、
「もう一つのデータも変わるってなんかお得なんじゃない?」という話ではない、という事です。
恥ずかしながら最初何が問題かわからず、実は便利機能かしら?と思ってしまったのは何を隠そうこの私ですが、そうではなく、今後はこの挙動は基本的に想定外に困る現象を引き起こす事が多い事を理解して、気をつけてコードを書いて行こうと思います。
全体的に非常に参考になった教育についての本。
特に第3章の答え合わせの仕方については普段そんな気がしながらも確信が持てていなかった箇所なので余計に納得できました。
Kindle版は今なら中古の最安値とほぼ同じ28%オフなので、Kindleで持てる分お買い得。リフロー型なのでハイライトも見返しも思いのまま。
コメント
[…] Pythonの変数について3 […]