1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

リストの代入についての覚え書き(再定義/代入/浅いコピー/深いコピー)

1
Posted at

概要

リストを**"""代入"""**したときの振る舞いの理解に怪しいところがあったので、整理するためにまとめておきます。

参考にさせていただいたサイト様

Python♪「参照渡し」「浅いコピー」「深いコピー」まずは理屈抜きで覚えよう。

前提

以下の内容は、イミュータブルオブジェクトには全く関係ありません。イミュータブルオブジェクトには数値、文字列、タプルなどがありますが、いずれもコピーした後のそれぞれの値は独立しています。

inmutable_copy.py
print('-----数値-----')
a = 1
b = 2
print(f'元データ:a = {a}, b = {b}')
b = a
print(f'コピー後:a = {a}, b = {b}')
a = 4
print(f'値変更後:a = {a}, b = {b}')
print("")

print('-----文字列-----')
a = 'abcd'
b = 'efgh'
print(f'元データ:a = {a}, b = {b}')
b = a 
print(f'コピー後:a = {a}, b = {b}')
a = 'xyz'
print(f'値変更後:a = {a}, b = {b}')
print("")

print('-----タプル-----')
a = (1, 2)
b = (3, 4)
print(f'元データ:a = {a}, b = {b}')
b = a
print(f'コピー後:a = {a}, b = {b}')
a = (10, 11)
print(f'値変更後:a = {a}, b = {b}')
出力
------数値型-----
元データ:a = 1, b = 2
コピー後:a = 1, b = 1
値変更後:a = 4, b = 1

-----文字列-----
元データ:a = abcd, b = efgh
コピー後:a = abcd, b = abcd
値変更後:a = xyz, b = abcd

-----タプル-----
元データ:a = (1, 2), b = (3, 4)
コピー後:a = (1, 2), b = (1, 2)
値変更後:a = (10, 11), b = (1, 2)

リストにおける「代入」

事例

最初にリストを用意して、コピーを行い、そのコピー元のリストを変更。コピー元とコピー先で比較を行ったり、数値計算を行ったりするというのはそこまで珍しい操作ではないと思います。

自分の場合は、何かの課題に取り組んでいる最中、初期状態と変更後の状態をそれぞれ保存したいと思い、以下のような操作を行いました。

list_sample.py
a = [1, 2]
b = a # コピー作成
print(f'a = {a}, b = {b}')

# .....aを用いた某かの操作.....
a[0] = 10

print(f'a = {a}, b = {b}')

当時の私の脳内では、元のリストaの最初の要素だけを変えているので、

a = [1, 2], b = [1, 2]
a = [10, 2], b = [1, 2]

となる予定だったのですが、実際の出力は以下のようになっていました。

a = [1, 2], b = [1, 2]
a = [10, 2], b = [10, 2]

変更したはずのない値がいつの間にか変わっている。最初に遭遇したとき、クエスチョンマークが動脈に詰まって頭が爆発しそうになったことを覚えています。

結局その課題自体は別の方法で達成したのですが、それ以来どうにもリストの代入に苦手意識がついて、利用を避けてきました。

リストにおける「代入」という言葉のヤバさ

色々と調べてサンプルコードをいくつか書いた結果、そもそもリストに「代入」するとホワッと認識をしていたのがよくなかったという結論に至りました。

私がリストの「代入」としてなんとなく認識していたものは、実際は「そのまま代入」「浅いコピー」「深いコピー」の3つに分かれていて、それらを混同していたことが意図せぬ動作の原因でした。(さらにそれとは別に「再定義」があり、私の中ではこちらも「代入」のカテゴリーに入っていました。)

今あらためて考えると、コピーや操作が一緒くたになっていて大分ヤバい認識をしていたのだと思います。

リストをそのまま代入した時

b = a

のように、リストをそのまま代入した時、その後のコピー元への操作によりコピー先への連動が起こる場合と、起こらない場合があります。

→再定義

リストをそのまま代入した後に、

a = [data]

という書き方でコピー元を操作したときには、コピー元の再定義を行ったことになります。

再定義では、いかなる場合も値が連動して変化することはありません。再定義を行った時点でそれらは別のリストになるためです。

redefinition.py
### 再定義による連動(一階層)
print("-----変数の再定義(一階層)-----")
a = [1, 2]
b = [3, 4]
print(f'<元データ>\na = {a}, b = {b}\n')

b = a  # そのまま代入
print(f'<b = a>\na = {a}, b = {b}\n')

a = ['wow'] # 再定義
print(f'<a = [\'wow\']>\na = {a}, b = {b}\n') # それぞれ独立
print('')

### 再定義による連動(複数階層)
print('-----変数の再定義(複数階層)-----')
a = [1, [2], [3, [4]]]
b = [10, [11], [11, [12]]]
print(f'<元データ>\na = {a}, b = {b}\n')

b = a  # そのまま代入
print(f'<b = a>\na = {a}, b = {b}\n')

a = [5, [6], [7, [8]]] # 再定義
print(f'<a = [5, [6], [7, [8]]]>\na = {a}, b = {b}\n') # それぞれ独立
print('')
-----変数の再定義(一階層)-----
<元データ>
a = [1, 2], b = [3, 4]

<b = a>
a = [1, 2], b = [1, 2]

<a = ['wow']>
a = ['wow'], b = [1, 2]


-----変数の再定義(複数階層)-----
<元データ>
a = [1, [2], [3, [4]]], b = [10, [11], [11, [12]]]

<b = a>
a = [1, [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a = [5, [6], [7, [8]]]>
a = [5, [6], [7, [8]]], b = [1, [2], [3, [4]]]

いずれの場合も、コピー元のリストaを変更したところで、コピー先のリストbには全く影響がないことが確認できます。

→要素の変更

リストをそのまま代入した後に、

a[i] = num

という書き方でコピー元の要素の変更を行うと、コピー元とコピー先が連動します。記事冒頭で行った書き方がこれにあたります。

change.py

print("-----要素の変更(一階層)-----")
a = [1, 2]
b = [3, 4]
print(f'<元データ> \na = {a}, b = {b}\n')

b = a  # コピー
print(f'<b = a>\na = {a}, b = {b}\n')

a[0]= 'wow' # 要素の変更
print(f'<a[0]= \'wow\'>\na = {a}, b = {b}\n') # 連動する
print('')

### 複数階層を持つリストにおける要素の変更による連動###
print('-----要素の変更(複数階層)-----')
a = [1, [2], [3, [4]]]
b = [10, [11], [11, [12]]]
print(f'<元データ>\na = {a}, b = {b}\n')

b = a  # 代入
print(f'<b = a>\na = {a}, b = {b}\n')

a[2] = ['wow', ['oops']] #要素の変更
print(f'<a[2] = [\'wow\', [\'oops\']]>\na = {a}, b = {b}\n') # 連動する
print('')
-----要素の変更(一階層)-----
<元データ> 
a = [1, 2], b = [3, 4]

<b = a>
a = [1, 2], b = [1, 2]

<a[0]= 'wow'>
a = ['wow', 2], b = ['wow', 2]


-----要素の変更(複数階層)-----
<元データ>
a = [1, [2], [3, [4]]], b = [10, [11], [11, [12]]]

<b = a>
a = [1, [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[2] = ['wow', ['oops']]>
a = [1, [2], ['wow', ['oops']]], b = [1, [2], ['wow', ['oops']]]

単一階層のリストでも複数階層のリストでも同じように連動していることが確認できます。これはaの再定義を行わないまま、aの要素を変更したために起こります。pythonの中ではbとaは同じリストを指しているのです。

浅いコピー

上記2つを学んだ後、再び脳に混乱をもたらしたのがこちらの浅いコピーです。リストをそのまま代入するのではなく、スライス表記の省略を利用して、

b = a[:]

のようにやるか、copy関数を利用して、

import copy
b = copy.copy(a)

のようにやるか。どちらかを行っても浅いコピーをやったことになります。

この浅いコピーと、対を成すのが深いコピーです。公式ドキュメントでは以下のように説明されています。

浅い (shallow) コピーと深い (deep) コピーの違いが関係するのは、複合オブジェクト (リストやクラスインスタンスのような他のオブジェクトを含むオブジェクト) だけです:
浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入します。
深いコピー (deep copy) は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入します。

引用元:copy --- 浅いコピーおよび深いコピー操作

20170910150231.jpg

99割私の知識不足が原因でしょうが、今ひとつ何を言っているか完全には理解しきれませんでした。新たな複合オブジェクトを作るところまでは同じで、そこから「参照を挿入」しているのか、「コピーを挿入」しているのかが違うようですが、それがどのような動作の違いを生むのかに繋げきれません。

とりあえず手を動かそうということで、書いたコードが以下になります。

shallowcopy.py
import copy
### 浅いコピー###
print('-----スライス表記の省略を利用-----')
a = [1, [2], [3, [4]]]
b = [3, [4], [5, [6]]]
print(f'<元データ>\na = {a}, b = {b}\n')

b = a[:] # 浅いコピーを行う
print(f'<b = a[:]>\na = {a}, b = {b}\n')

a[0] = 'wow' # 連動しない
print(f'<a[0] = \'wow\'>\na = {a}, b = {b}\n')

a[1][0] = 'oops' # 連動する
print(f'<a[1][0] = \'oops\'>\na = {a}, b = {b}\n')

a[2][0] = 'whoaaaaaa' # 連動する
print(f'<a[2][0] = \'whoaaaaaa\'>\na = {a}, b = {b}\n')

a[2][1][0] = '!?!!!????!' # 連動する
print(f'<a[2][1][0] = \'!?!!!????!\'>\na = {a}, b = {b}\n')

print('-----copy関数利用-----')
a = [1, [2], [3, [4]]]
b = [3, [4], [5, [6]]]
print(f'<元データ>\na = {a}, b = {b}\n')

b = copy.copy(a) # 浅いコピーを行う
print(f'<b = copy.copy(a)\na = {a}, b = {b}\n')

a[0] = 'wow' # 連動しない
print(f'<a[0] = \'wow\'>\na = {a}, b = {b}\n')

a[1][0] = 'oops' # 連動する
print(f'<a[1][0] = \'oops\'>\na = {a}, b = {b}\n')

a[2][0] = 'whoaaaaaa' # 連動する
print(f'<a[2][0] = \'whoaaaaaa\'>\na = {a}, b = {b}\n')

a[2][1][0] = '!?!!!????!' # 連動する
print(f'<a[2][1][0] = \'!?!!!????!\'>\na = {a}, b = {b}\n')
-----スライス表記の省略を利用-----
<元データ>
a = [1, [2], [3, [4]]], b = [3, [4], [5, [6]]]

<b = a[:]>
a = [1, [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[0] = 'wow'>
a = ['wow', [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[1][0] = 'oops'>
a = ['wow', ['oops'], [3, [4]]], b = [1, ['oops'], [3, [4]]]

<a[2][0] = 'whoaaaaaa'>
a = ['wow', ['oops'], ['whoaaaaaa', [4]]], b = [1, ['oops'], ['whoaaaaaa', [4]]]

<a[2][1][0] = '!?!!!????!'>
a = ['wow', ['oops'], ['whoaaaaaa', ['!?!!!????!']]], b = [1, ['oops'], ['whoaaaaaa', ['!?!!!????!']]]

-----copy関数利用-----
<元データ>
a = [1, [2], [3, [4]]], b = [3, [4], [5, [6]]]

<b = copy.copy(a)
a = [1, [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[0] = 'wow'>
a = ['wow', [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[1][0] = 'oops'>
a = ['wow', ['oops'], [3, [4]]], b = [1, ['oops'], [3, [4]]]

<a[2][0] = 'whoaaaaaa'>
a = ['wow', ['oops'], ['whoaaaaaa', [4]]], b = [1, ['oops'], ['whoaaaaaa', [4]]]

<a[2][1][0] = '!?!!!????!'>
a = ['wow', ['oops'], ['whoaaaaaa', ['!?!!!????!']]], b = [1, ['oops'], ['whoaaaaaa', ['!?!!!????!']]]

どちらの方法で浅いコピーを行っても結果はかわっていません。ここで注目したのは、

<a[0] = 'wow'>
a = ['wow', [2], [3, [4]]], b = [1, [2], [3, [4]]]

の部分です。リストを直接代入したときは逐一連動していました。一方、浅いコピーを行ったときは、一番浅い?要素をいじったときは連動しませんでした。これが公式ドキュメントにあった、

(可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入します。

における、参照を挿入できなかった場合なのでしょうか。いずれにしろ、一番浅い要素の変更は連動しないというのがそのまま代入との違いだというのは理解できました。

深いコピー

copy関数を利用し、

import copy
b = copy.deepcopy(a)

のように操作した場合、深いコピーを行ったことになります。この場合、コピー元とコピー先は完全に独立したリストになり、一切値が連動しなくなります。

deepcopy.py
import copy
print('-----深いコピー-----')
a = [1, [2], [3, [4]]]
b = [3, [4], [5, [6]]]
print(f'<元データ>\na = {a}, b = {b}\n')

b = copy.deepcopy(a) #深いコピーを行う
print(f'b = copy.deepcopy(a)\na = {a}, b = {b}\n')

a[0] = 'wow' # 連動しない
print(f'<a[0] = \'wow\'>\na = {a}, b = {b}\n')

a[1][0] = 'oops' # 連動しない
print(f'<a[1][0] = \'oops\'>\na = {a}, b = {b}\n')

a[2][0] = 'whoaaaaaa' # 連動しない
print(f'<a[2][0] = \'whoaaaaaa\'>\na = {a}, b = {b}\n')

a[2][1][0] = '!?!!!????!' # 連動しない
print(f'<a[2][1][0] = \'!?!!!????!\'>\na = {a}, b = {b}\n')
-----深いコピー-----
<元データ>
a = [1, [2], [3, [4]]], b = [3, [4], [5, [6]]]

b = copy.deepcopy(a)
a = [1, [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[0] = 'wow'>
a = ['wow', [2], [3, [4]]], b = [1, [2], [3, [4]]]

<a[1][0] = 'oops'>
a = ['wow', ['oops'], [3, [4]]], b = [1, [2], [3, [4]]]

<a[2][0] = 'whoaaaaaa'>
a = ['wow', ['oops'], ['whoaaaaaa', [4]]], b = [1, [2], [3, [4]]]

<a[2][1][0] = '!?!!!????!'>
a = ['wow', ['oops'], ['whoaaaaaa', ['!?!!!????!']]], b = [1, [2], [3, [4]]]

単一階層か、複数階層かを問わず、一切値が連動しないことが確認できます。

まとめ

私が求めていた用途に一番近いのは「深いコピー」でした。比較を行うことが目的なので、完全に別の存在になって、値も全く連動しないほうが都合が良いです。

余談

考察:識別値の違いが影響している?

この記事のためのサンプルコードを書いている最中に、pythonには識別値なる概念があることを知りました。公式ドキュメントには以下のように書かれています。

id(object)

オブジェクトの "識別値" を返します。この値は整数で、このオブジェクトの有効期間中は一意かつ定数であることが保証されています。有効期間が重ならない 2 つのオブジェクトは同じ id() 値を持つかもしれません。
引用元:組み込み関数

また、Qiitaにも非常にわかりやすい記事がありました。(Python のリストの扱いで注意すること)

これを見て、「コピーやら代入やらの際に、識別値の扱いが変わっているんじゃないの?」と思って書いたのが以下のサンプルコード。

ごちゃごちゃしたので折りたたみ
  • そのまま代入した場合
idcopy1.py
import copy

a = [1, 2,[3, 4]]
b = [5, 6]

print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

b = a # 値も識別値も同じ

print('<b = a>')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}\n\n')

a[2][0] = 'Wow' # リストも要素も識別値は同じ

print('a[2][0] = \'Wow\'')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}')
a:value = [1, 2, [3, 4]] ,id = 1670972282120
b:value = [5, 6] ,id = 1670972283720

<b = a>
a:value = [1, 2, [3, 4]] ,id = 1670972282120
b:value = [1, 2, [3, 4]] ,id = 1670972282120

a[0]:value = 1, id = 140720288407952
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = [3, 4], id = 1670972282056
b[2]:value = [3, 4], id = 1670972282056
a[2][0]:value = 3, id = 140720288408016
b[2][0]:value = 3, id = 140720288408016
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048


a[2][0] = 'Wow'
a:value = [1, 2, ['Wow', 4]] ,id = 1670972282120
b:value = [1, 2, ['Wow', 4]] ,id = 1670972282120

a[0]:value = 1, id = 140720288407952
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = ['Wow', 4], id = 1670972282056
b[2]:value = ['Wow', 4], id = 1670972282056
a[2][0]:value = Wow, id = 1670972194416
b[2][0]:value = Wow, id = 1670972194416
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048

こちらは予想通り。idも値も一致しています。

  • 浅いコピーを行った場合.
idcopy2.py
import copy

a = [1, 2,[3, 4]]
b = [5, 6]

print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

b = copy.copy(a) # 値は同じだが、識別値が違う

print('<b = copy.copy(a)>')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}\n\n')

a[2][0] = 'Wow' # 値は同じだが識別値が違う

print('a[2][0] = \'Wow\'')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}\n\n')

a[0] = 'oops' # 値も識別値も違う

print('a[0] = \'oops\'')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}')
a:value = [1, 2, [3, 4]] ,id = 2030813649736
b:value = [5, 6] ,id = 2030813650760

<b = copy.copy(a)>
a:value = [1, 2, [3, 4]] ,id = 2030813649736 ←値は同じだが識別値が違う
b:value = [1, 2, [3, 4]] ,id = 2030813649864

↓要素の方は値も識別値も完全に一致
a[0]:value = 1, id = 140720288407952
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = [3, 4], id = 2030813649672
b[2]:value = [3, 4], id = 2030813649672
a[2][0]:value = 3, id = 140720288408016
b[2][0]:value = 3, id = 140720288408016
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048


a[2][0] = 'Wow'
a:value = [1, 2, ['Wow', 4]] ,id = 2030813649736 ←値は同じだが識別値は違う
b:value = [1, 2, ['Wow', 4]] ,id = 2030813649864

↓要素の方は値も識別値も完全に一致
a[0]:value = 1, id = 140720288407952
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = ['Wow', 4], id = 2030813649672
b[2]:value = ['Wow', 4], id = 2030813649672
a[2][0]:value = Wow, id = 2030813527920
b[2][0]:value = Wow, id = 2030813527920
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048


a[0] = 'oops'
a:value = ['oops', 2, ['Wow', 4]] ,id = 2030813649736 ←値も識別値も違う
b:value = [1, 2, ['Wow', 4]] ,id = 2030813649864

↓要素の方の値と識別値も一致しなくなる
a[0]:value = oops, id = 2030813648176
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = ['Wow', 4], id = 2030813649672
b[2]:value = ['Wow', 4], id = 2030813649672
a[2][0]:value = Wow, id = 2030813527920
b[2][0]:value = Wow, id = 2030813527920
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048

浅いコピーを行うと、「リストの値は同じだが識別値は違う」一方で、「要素の方は値も識別値も同じ」という状況が起きるようです。

ガワは別の場所にあるのに、中身自体は同じ場所を指している。無意識のうちに同一性と同値性を混同していたのとあわせて、私が混乱している原因はここにあるような気がします。

ただ、結局一番浅い階層の要素を変更したときに連動しない理由はよくわからないままです。これが浅いコピーの特性だよ!と言われればそれまでなのですが。

  • 深いコピーを行った場合
idcopy3.py
import copy

a = [1, 2,[3, 4]]
b = [5, 6]

print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

b = copy.deepcopy(a) # 値は同じだが識別値は異なる

print('<b = copy.deepcopy(a)>')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}') 
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}\n\n')

a[2][0] = 'Wow' # 値も識別値も異なる

print('a[2][0] = \'Wow\'')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}\n\n')

a[0] = [123] # 値も識別値も異なる

print('a[0] = [123]')
print(f'a:value = {a} ,id = {id(a)}')
print(f'b:value = {b} ,id = {id(b)}\n')

print(f'a[0]:value = {a[0]}, id = {id(a[0])}')
print(f'b[0]:value = {b[0]}, id = {id(b[0])}\n')
print(f'a[1]:value = {a[1]}, id = {id(a[1])}')
print(f'b[1]:value = {b[1]}, id = {id(b[1])}\n')

print(f'a[2]:value = {a[2]}, id = {id(a[2])}')
print(f'b[2]:value = {b[2]}, id = {id(b[2])}')
print(f'a[2][0]:value = {a[2][0]}, id = {id(a[2][0])}')
print(f'b[2][0]:value = {b[2][0]}, id = {id(b[2][0])}')
print(f'a[2][1]:value = {a[2][1]}, id = {id(a[2][1])}')
print(f'b[2][1]:value = {b[2][1]}, id = {id(b[2][1])}')
a:value = [1, 2, [3, 4]] ,id = 2068332288712
b:value = [5, 6] ,id = 2068332289992

<b = copy.deepcopy(a)>
a:value = [1, 2, [3, 4]] ,id = 2068332288712
b:value = [1, 2, [3, 4]] ,id = 2068332288840

a[0]:value = 1, id = 140720288407952
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = [3, 4], id = 2068332288648 ←要素の値は同じだが識別値が異なる
b[2]:value = [3, 4], id = 2068332185672 ←
a[2][0]:value = 3, id = 140720288408016
b[2][0]:value = 3, id = 140720288408016
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048


a[2][0] = 'Wow'
a:value = [1, 2, ['Wow', 4]] ,id = 2068332288712
b:value = [1, 2, [3, 4]] ,id = 2068332288840

a[0]:value = 1, id = 140720288407952
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = ['Wow', 4], id = 2068332288648
b[2]:value = [3, 4], id = 2068332185672
a[2][0]:value = Wow, id = 2068332166960
b[2][0]:value = 3, id = 140720288408016
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048


a[0] = [123]
a:value = [[123], 2, ['Wow', 4]] ,id = 2068332288712
b:value = [1, 2, [3, 4]] ,id = 2068332288840

a[0]:value = [123], id = 2068332289992
b[0]:value = 1, id = 140720288407952

a[1]:value = 2, id = 140720288407984
b[1]:value = 2, id = 140720288407984

a[2]:value = ['Wow', 4], id = 2068332288648
b[2]:value = [3, 4], id = 2068332185672
a[2][0]:value = Wow, id = 2068332166960
b[2][0]:value = 3, id = 140720288408016
a[2][1]:value = 4, id = 140720288408048
b[2][1]:value = 4, id = 140720288408048

リスト自体の「要素は同じだが識別値は違う」というのは浅いコピーですでに起きていたことです。こちらで気になるのは、「最初にコピーした段階で、要素に含まれるリストの識別値がすでに異なっている」こと。

おそらくこれが浅いコピーとの動作の違いを生むんでしょうが、現段階の私の知識と思考能力ではそこが繋げられないです。悲しい。

リスト自体の同値性と同一性、そして要素の方の同値性と同一性がそれぞれ少しずつ異なっています。

いずれの方法も、何かしら特定の場面を想定した仕様だとは思うのですが、特に浅いコピーについては、「一番浅い階層の要素の変更は連動しない」というのはどのような状況を想定しているのかさっぱりです。

単純に経験不足なのでしょうね。存在意義が理解できたらまた戻ってきて編集しようと思います。

1
2
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?