🍎

python3における代入元の変更の影響

2023/11/12に公開

変数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