オブジェクトの共有とコピー
入れ子の辞書とリストを三方式で扱い、内部の変更と参照先の置換を比較します。デフォルト引数の共有と、タプルの中にあるリストの変更も確認します。
説明
代入はオブジェクトへ別の名前を結び付ける操作で、自動的なコピーではありません。外側をコピーしても、内側の可変オブジェクトが共有される場合があります。
入れ子の辞書とリストを三方式で扱い、内部の変更と参照先の置換を比較します。デフォルト引数の共有と、タプルの中にあるリストの変更も確認します。
1共有する別名と二種類のコピー
scoresにリストを持つ辞書を用意します。aliasは同じ辞書、shallowは浅いコピー、deepは深いコピーです。
import copy; original = {"scores": [10, 20], "label": "A"}入れ子の初期データ; alias = original同じ辞書を参照; shallow = original.copy()外側をコピー; deep = copy.deepcopy(original)内側もコピー; print(alias is original, shallow is original, shallow["scores"] is original["scores"], deep["scores"] is original["scores"])外側と内側の同一性外側の辞書はaliasだけが同一ですが、内側scoresはshallowでも共有します。deepでは内側のリストも別になっています。
2共有する内側を変更
originalのscoresへ30を追加します。
original["scores"].append(30)内側のリストを更新; print(original, alias, shallow, deep)変更が見える範囲original・alias・shallowには30が現れ、deepは10と20のままです。浅いコピーの外側が別でも、共有している内側を変更すると両方から見えます。
3内側の参照を別のリストへ置き換える
今度はリスト内容を変更せず、originalのscoresを新しい[99]へ置き換えます。
original["scores"] = [99]辞書の参照先を置換; print(original, alias, shallow, deep)以前のリストが残る場所aliasは同じ辞書なので99へ変わります。shallowは以前のリスト[10, 20, 30]への参照を保持し、deepは[10, 20]です。内容の変更と参照先の置換は別の操作です。
4深いコピーも共有関係を保つ場合がある
同じ子リストを二箇所で参照する構造を深くコピーします。
child = [1]; source = [child, child]同じ子を二度参照; cloned = copy.deepcopy(source)構造を深くコピー; cloned[0].append(2)コピー側の子を変更; print(source, cloned, cloned[0] is cloned[1], cloned[0] is child)コピー内の共有と元からの分離コピー内の二箇所は同じ新しい子を参照し、どちらも[1, 2]に見えます。元のchildは[1]です。deepcopyは既にコピーした対象を記録し、元の共有関係もコピー内で保持します。
5デフォルトのリストへ追加する関数
引数を省略したときに使うリストを、デフォルト値として定義します。
def collect(value, items=[]):定義時に作るデフォルト
items.append(value)受け取ったリストを変更
return items同じリストを返すデフォルト式[]は関数定義時に一度評価されます。省略した呼出しが毎回新しい空リストを作るわけではありません。
6引数を省略した呼び出しの間でリストが共有される
二回呼び、その結果を後から表示します。
first = collect("A"); second = collect("B")デフォルトを二度使用; print(first, second, first is second)結果同士の共有どちらも[A, B]です。firstも同じリストを参照しているため、二回目の追加が見えます。最初に戻された結果が独立した記録として保存されたわけではありません。
7呼出しごとに新しく作る
省略時の印にNoneを使い、関数内で空リストを作ります。
def collect_new(value, items=None省略時の印):
if items is None:
items = []省略時だけ新規作成
items.append(value)受取先を更新
return items更新したリストNoneの場合だけ新しく作るため、明示的に渡した空リストはそのまま使えます。items or []では、渡した空リストも別のリストへ置き換わるので、ここではis Noneを使います。
8省略と明示的な共有を比較
省略した二呼出しと、外側のリストを渡した呼出しを確認します。
a = collect_new("A"); b = collect_new("B")別々に新規作成; external = []; returned = collect_new("C", external)明示的にリストを共有; print(a, b, a is b)省略時の独立性; print(external, returned is external)明示した共有省略時は[A]と[B]で別のリストです。externalを渡した呼出しは同じリストへCを追加し、明示的な共有は維持します。
9タプルの中のリストは変更できる
タプルは位置の参照を差し替えられませんが、含まれるリストまで不変にするものではありません。
record = ("A", [10])リストを含むタプル; record[1].append(20)参照先のリストを変更; print(record)タプルから見える変化タプルの二番目は同じリストのまま、その内容が[10, 20]へ変わっています。不変という性質がどの階層のどの操作に適用されるか確認します。
10タプルの位置そのものは差し替えられない
二番目を別のリスト[99]へ代入してみます。
record[1] = [99]タプルの位置への再代入TypeErrorになります。直前のappendは内側のリストの操作、今回はタプルの位置への代入です。同じrecord[1]が出ても、変更対象が異なります。
まとめ
コピーの外側だけでなく、内側の共有関係まで確認します。
デフォルト値や不変コンテナーでも、内部の可変値が共有される場合があります。
操作履歴と解説を振り返れます。
Python 3
import copy; original = {"scores": [10, 20], "label": "A"}; alias = original; shallow = original.copy(); deep = copy.deepcopy(original); print(alias is original, shallow is original, shallow["scores"] is original["scores"], deep["scores"] is original["scores"])True False True False
original["scores"].append(30); print(original, alias, shallow, deep){'scores': [10, 20, 30], 'label': 'A'} {'scores': [10, 20, 30], 'label': 'A'} {'scores': [10, 20, 30], 'label': 'A'} {'scores': [10, 20], 'label': 'A'}original["scores"] = [99]; print(original, alias, shallow, deep){'scores': [99], 'label': 'A'} {'scores': [99], 'label': 'A'} {'scores': [10, 20, 30], 'label': 'A'} {'scores': [10, 20], 'label': 'A'}child = [1]; source = [child, child]; cloned = copy.deepcopy(source); cloned[0].append(2); print(source, cloned, cloned[0] is cloned[1], cloned[0] is child)[[1], [1]] [[1, 2], [1, 2]] True False
def collect(value, items=[]):
items.append(value)
return itemsfirst = collect("A"); second = collect("B"); print(first, second, first is second)['A', 'B'] ['A', 'B'] True
def collect_new(value, items=None):
if items is None:
items = []
items.append(value)
return itemsa = collect_new("A"); b = collect_new("B"); external = []; returned = collect_new("C", external); print(a, b, a is b); print(external, returned is external)['A'] ['B'] False ['C'] True
record = ("A", [10]); record[1].append(20); print(record)('A', [10, 20])record[1] = [99]Traceback (most recent call last): File "<console>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
演習完了