ログインボックスを表示します

検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
当サイトのドメイン名は " getwebtips.net " です。
トップレベルドメインは .net であり、他の .com / .shop といったトップレベルドメインのサイトとは一切関係ありません。
practical_python_design_patterns

Practical Python Design Patterns - Python で学ぶデザインパターン: The Prototype Pattern - Part. 3「浅いコピー vs. 深いコピー」の巻 投稿一覧へ戻る

Published 2022年5月21日21:08 by T.Tsuyoshi

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

Shallow Copy vs. Deep Copy
(浅いコピー vs. 深いコピー)

Python における変数 (variables) の扱いは、他のプログラミング言語と若干異なっているところがあります。integers や strings といった基本的なデータ型は別として、1つ1つの変数が表しているのは、他のプログラミング言語ではその変数の値を入れている「箱」として比喩されますが、Python では「タグ」や「ラベル」に近いものです。すなわち、実際の値が格納されているメモリの番地を指す「ポインタ」がその変数の値になっています。
次の例を見てください:
a = [i for i in range(1, 6)] # ①
print(f'[a]: {a}')
b = a # ②
print(f'[b]: {b}')

b.append(6) # ③
print(f'[a]: {a}')
print(f'[b]: {b}')
変数 a は 1 から 5 までの数値を要素とするリストを「指して」います
変数 b に変数 a の値 (リストを「指す」値) を入れています
変数 b が「指す」リストに対して 6 を追加しています
さて、このコードを実行した際の出力結果はどうなるでしょう?
[a]: [1, 2, 3, 4, 5]
[b]: [1, 2, 3, 4, 5]
[a]: [1, 2, 3, 4, 5, 6]
[b]: [1, 2, 3, 4, 5, 6]
リスト a にもリスト b にも 6 が追加されていることに驚かれる方もいるでしょう。しかし、「リスト a」「リスト b」などと書いていますが、リストが2つ存在するわけではありません。変数 a と変数 b が指しているリストはメモリ上の同じリストです。メモリ上に a という箱があってその中に値としてのリストが入っているわけではなく、メモリ上の a にはメモリ上のほかの場所にあるリストを指す「番地」が入っているだけです。そして b に関しても同じです。ですからリストの値を変更すれば、それを「参照」している変数 a と b の内容も変化するわけです。

Shallow Copy
(浅いコピー)

では、リストの「コピー」が必要な場合はどうしたらよいのでしょうか?つまり、最終的に、内容が同じ2つの異なるリストを取得したい場合はどうしましょう?
1つの方法はリストに対して slice (スライス) 操作を実行することです:
a = [i for i in range(1, 6)]
print(f'[a]: {a}')
b = a[:] # ①
print(f'[b]: {b}')
b.append(6)
print(f'[a]: {a}')
print(f'[b]: {b}')
先程の例と異なっているのはこの行だけです。変数 a が指しているリストと内容が全く同じ新しいリストが返ってきて変数 b で参照できるようになります。
リスト(を含むシーケンス全般)に対するスライス操作は非常に便利です。上記のように a[:] で a と全く同じ内容のものを取得できますし、a[2:] では、最初の2つの要素を除くリストを取得できます。反対に最後の2つの要素を除きたければ a[:-2] となります。
このコードの実行結果は以下になります:
[a]: [1, 2, 3, 4, 5]
[b]: [1, 2, 3, 4, 5]
[a]: [1, 2, 3, 4, 5]
[b]: [1, 2, 3, 4, 5, 6]
こういったスライス操作は、そのリストが shallow list (浅いリスト: 単純な型 [int, str 等] だけを含むリスト) であり、複雑なオブジェクト (complex objects: list や dictionary 等) への参照を含まない場合であれば上手く機能します。

Dealing with Nested Structures
(ネスト構造体への対処)

下記のコードの出力はどうなるでしょう?
lst1 = ['a', 'b', ['ab', 'ba']]
lst2 = lst1[:]
lst2[0] = 'c'
print(f'[lst1] {lst1}')
print(f'[lst2] {lst2}')
これは deep list (要素として complex objects を含むもの) の例です。出力結果は以下のようになります:
[lst1] ['a', 'b', ['ab', 'ba']]
[lst2] ['c', 'b', ['ab', 'ba']]
予想通りですよね? lst2 は lst1 のコピーを参照していますから、lst2 への変更は lst1 へ影響を及ぼしません。では、リストの要素として含まれているリスト、に変更を加えた場合はどうなるでしょうか?
lst1 = ['a', 'b', ['ab', 'ba']]
lst2 = lst1[:]
lst2[2][1] = 'd'
print(f'[lst1] {lst1}')
print(f'[lst2] {lst2}')
実行結果は以下の通りになります:
[lst1] ['a', 'b', ['ab', 'd']]
[lst2] ['a', 'b', ['ab', 'd']]
lst2 は lst1 のコピーを参照しているにもかかわらず、lst2 に対して行った変更が lst1 にも反映されています。これはどういうことなのでしょうか?
lst1 は3つの要素を含むリストです。lst1[0] は 'a'、lst1[1] は 'b'、lst1[2] は ['ab', 'ba'] というリスト「に対する参照」です。
Shallow copy (浅いコピー) を行う、ということは、そのリストに含まれる「第1レベルの要素」をコピーする、ということです。ですから、'a', 'b' という「値」に関してはその値自体のコピーを作成しますが、['ab', 'ba] というリストに対しては、それに対する「参照」をコピーするだけで、実体をコピーしているわけではないのです。すなわち lst1 の内容をタイプ別に表現すると [実体, 実体, 参照] となり、lst2 にはこれがコピーされている、ということになります。つまり、リストに含まれるリストの実体がクローンされてコピーに含まれる、ということではありません。
もうこのコードの実行結果が説明できますね? lst1 と lst2 に含まれている 'a' と 'b' はそれぞれ実体がコピーされたものですから「別物」です。しかし ['ab', 'ba'] に関してはこのリストに対する参照がコピーされたものですから、結果的にメモリ上に存在する同じリストを指していることになります。よって、lst2[2] が参照しているリストへの変更は lst1[2] が参照しているリストへの変更と同じことであることから、このような出力結果になったわけです。
浅いコピー (shallow copy) とは、ネストしている構造体の第1レベルの要素のコピー、ということです。

Deep Copy
(深いコピー)

結局、オブジェクトの完全なクローンを作成するには通常のコピー (shallow copy) では不十分だ、ということです。ではどうしたら、リストに含まれるリストのような、あるオブジェクトに含まれる全てのものの完全なコピーを Python で実現したらよいのでしょうか?
Python の標準ライブラリには copy モジュールが含まれていて、その中で deepcopy() メソッドが提供されています。このメソッドは、浅いリストはもちろん、リストを含むリストやクラスオブジェクトのような complex/compound objects の完全な deep copy を作成してくれます。この deepcopy() メソッドを利用して先程のコードを書き換えてみましょう:
from copy import deepcopy

lst1 = ['a', 'b', ['ab', 'ba']]
lst2 = deepcopy(lst1)
lst2[2][1] = 'd'
print(f'[lst1] {lst1}')
print(f'[lst2] {lst2}')
結果は...
[lst1] ['a', 'b', ['ab', 'ba']]
[lst2] ['a', 'b', ['ab', 'd']]
期待通りですね。
ちょっと長い遠回りになってしまいましたが、これで我々の RTS ゲームにプロトタイプパターンを適用する準備が整いました。

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)