python3における代入元の変更の影響
変数aをbに代入したあとで変数aを変更した場合の挙動を型別に見ていく
イミュータブルな型(int, tuple等)の場合
a = 3
b = a
a = 99
print(f"a = {a}")
print(f"b = {b}")
上記の実行結果は下記。
元の変数aが変更されても、bは影響を受けない。
a = 99
b = 3
ミュータブルな型(list)の場合
list全体を置き換えた場合
a = ["zero", "one", "two"]
b = a
a = ["replaced"]
print(f"a = {a}")
print(f"b = {b}")
上記の実行結果は下記。
こちらの場合も変数bは影響を受けない。
a = ['replaced']
b = ['zero', 'one', 'two']
b = aによって変数bが変数aと同じlistオブジェクトを参照するようになった。
ただa = ["replaced"]
によってaが別のlistオブジェクトを新たに作成し、そちらを参照するようになったため、bはaの変更の影響を受けていない。
listの一部要素を置き換えた場合
a = ["zero", "one", "two"]
b = a
a[0] = "replaced"
print(f"a = {a}")
print(f"b = {b}")
上記の実行結果は下記。
変数bの要素が変更されている。
a = ['replaced', 'one', 'two']
b = ['replaced', 'one', 'two']
ここでもb = aによって変数bが変数aと同じlistオブジェクトを参照するようになっている。
その後aの参照先アドレスを変更せず、参照先に格納されている要素を変更したためbがaの変更の影響を受けた。
代入した変数を変更した際に影響を避ける方法
変数aがイミュータブルな型のみによって構成されている場合
a.copy()を使用する。
a = ["zero", "one", "two"]
b = a.copy()
a[0] = "replaced"
print(f"a = {a}")
print(f"b = {b}")
上記の実行結果は下記。
変数bが変更されない。
a = ['replaced', 'one', 'two']
b = ['zero', 'one', 'two']
これは変数aが参照している先のオブジェクトがshallow copyされ、
変数bがそちらを参照するようになったため。
参照しているオブジェクトが違うため変数bは変数aの変更の影響を受けない。
変数aにミュータブルな型の値が含まれる場合
a.deepcopy()を使用する。
変数aにミュータブルな型の値が含まれる場合、変数aに格納されているのはその値への参照なので.copy()では変数bは変数aの変更の影響を受けてしまう。
a = ["zero", [1, 2], "two"]
b = a.copy()
a[0] = "replaced"
a[1][0] = 99
print(f"a = {a}")
print(f"b = {b}")
実行結果は下記。
イミュータブルな値は変数aの変更の影響を受けないが、ミュータブルな値は影響を受ける。
a = ['replaced', [99, 2], 'two']
b = ['zero', [99, 2], 'two']
これを避けるには下記のようにa.deepcopy()を使用する。
a.deepcopy()はaが参照している先のlistに含まれる各オブジェクトがミュータブルかどうか確認し、各オブジェクトの実体をコピーする。
import copy
a = ["zero", [1, 2], "two"]
b = copy.deepcopy(a)
a[0] = "replaced"
a[1][0] = 99
print(f"a = {a}")
print(f"b = {b}")
実行結果は下記。
変数aの変更の影響を受けていない。
a = ['replaced', [99, 2], 'two']
b = ['zero', [1, 2], 'two']
Discussion