数値計算をしていると、次のような操作をしたいときがあります。
- ある配列を複製して、複製元の配列を変えずに複製した配列の成分の値のみを変えたい。
- 全ての成分が同じ値(例えば0)である配列を作って、その配列のとある成分の値のみを変えたい。
pythonで数値計算をしていたところ、listやndarrayを複製したり、各成分の値を書き換えたりする際に少しつまずいたので、list、ndarrayのその辺りの性質についてまとめます。listやndarrayの詳しい仕様については触れていません。
複製速度についてはこちらをご覧ください。
結論
ある配列を複製する際、複製した配列の各成分の値を変えても元の配列の値が変わらない複製方法を「配列の複製」にまとめました。また、とある成分の値を変えても他の成分の値が変わらない0埋めされた配列の作製方法は「0埋めされた配列の作製」にまとめてあります。
配列の複製
配列aを複製した配列copy_aを作り、aの各成分の値を変えずにcopy_aの各成分の値のみを変えるには、
- aが1次元listの場合は次の方法(2)〜(5)でcopy_aを作りましょう。
copy_a = a[:] # (2)
copy_a = a.copy() # (3)
copy_a = list(a) # (4)
copy_a = [elem for elem in a] # (5)
- aが2次元listの場合は次の方法(5.2)〜(5.5)が有効です。
copy_a = [elem[:] for elem in a] # (5.2)
copy_a = [elem.copy() for elem in a] # (5.3)
copy_a = [list(elem) for elem in a] # (5.4)
copy_a = [[_elem for _elem in elem] for elem in a] # (5.5)
今回は扱いませんでしたが、copyモジュールのdeepcopyを使うことでも複製元の各成分の値を変えずに複製したlistの各成分の値のみを変えることが出来るようです(リンクを参照)。
- 一方で、aがndarrayの場合は、aが1次元、2次元であるかを問わず次の方法(3)〜(5)で複製元aの値を変えずに各成分の値を変えられるcopy_aを作ることが出来ます。
copy_a = a.copy() # (3)
copy_a = np.array(a) # (4)
copy_a = np.array([elem for elem in a]) # (5)
1次元ndarrayに対して、1次元listのように方法(2)が使えない点に注意しましょう。
0埋めされた配列の作製
ある成分の値を変えても他の成分の値が変わらない0埋めされた配列を作るには、
- 1次元listの場合、次の方法(1)、(2)が有効です。
# Kは自然数
a = [0 for _ in range(K)] # (1)
a = [0]*K # (2)
- 2次元listの場合は方法(1.1)、(1.2)を使いましょう。
a = [[0 for _ in range(K)] for _ in range(K)] # (1.1)
a = [[0]*K for _ in range(K)] # (1.2)
# a = [[0]*K]*K # (2.2)
方法(2.2)が使えないことには注意して下さい。
- 1次元ndarrayの場合は、次の方法が有効です。
a = np.zeros(K, int)
a = [0 for _ in range(K)] # (1)
a = [0]*K # (2)
- 2次元ndarrayの場合は次の方法が使えます。
a = np.zeros((K, K), int)
a = np.array([[0 for _ in range(K)] for _ in range(K)]) # (1.1)
a = np.array([[0]*K for _ in range(K)]) # (1.2)
a = np.array([[0]*K]*K) # (2.2)
2次元listの場合は、方法(2.2)は有効でありませんでしたが、作った2次元listをndarrayに変換すると、とある成分の値を変えても他の成分の値が変わらない0埋めされた配列になります。
実験1
実験1では、1次元list・ndarray及び2次元list・ndarrayの様々な複製方法を試しました。
1次元listの複製
1次元list aを複製したlist copy_aを作って、copy_aの第0成分の値を変える場合、
>>> a = [0, 1]
>>> copy_a = a # (1)
>>> id(copy_a) == id(a)
True
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [1, 1]
copy_a = [1, 1]
>>> id(copy_a[0]) == id(a[0])
True
>>> a = [0, 1]
>>> copy_a = a[:] # (2)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0, 1]
copy_a = [1, 1]
>>> id(copy_a[0]) == id(a[0])
False
>>> a = [0, 1]
>>> copy_a = a.copy() # (3)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0, 1]
copy_a = [1, 1]
>>> id(copy_a[0]) == id(a[0])
False
>>> a = [0, 1]
>>> copy_a = list(a) # (4)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0, 1]
copy_a = [1, 1]
>>> id(copy_a[0]) == id(a[0])
False
>>> a = [0, 1]
>>> copy_a = [elem for elem in a] # (5)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0, 1]
copy_a = [1, 1]
>>> id(copy_a[0]) == id(a[0])
False
上述のように、(1)以外の方法ならば、copy_aの成分の値を変えてもaの成分の値は変わりません。
1次元ndarrayの複製
一方で、aが1次元ndarrayの場合は以下のような結果になります。
>>> a = np.array([0, 1])
>>> copy_a = a # (1)
>>> id(copy_a) == id(a)
True
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [1 1]
copy_a = [1 1]
>>> id(copy_a[0]) == id(a[0])
True
>>> a = np.array([0, 1])
>>> copy_a = a[:] # (2)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [1 1]
copy_a = [1 1]
>>> id(copy_a[0]) == id(a[0])
True
>>> a = np.array([0, 1])
>>> copy_a = a.copy() # (3)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0 1]
copy_a = [1 1]
>>> id(copy_a[0]) == id(a[0])
True
>>> a = np.array([0, 1])
>>> copy_a = np.array(a) # (4)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0 1]
copy_a = [1 1]
>>> id(copy_a[0]) == id(a[0])
True
>>> a = np.array([0, 1])
>>> copy_a = np.array([elem for elem in a]) # (5)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> copy_a[0] = 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [0 1]
copy_a = [1 1]
>>> id(copy_a[0]) == id(a[0])
True
aがlistだった場合と異なる点は、
- (2)の方法で複製した場合にcopy_aの成分の値を変えると、aの成分の値も変わる。
- (3)〜(5)の方法では、copy_aの成分の値を変えてもaの成分の値は変わらないが、変えた成分のidは等しい。
の2点です。
2次元listの複製
2次元list aを複製したlist copy_aを作り、aの値を変えずに、copy_aの(0,0)成分の値のみを変えたいとします。1次元listの場合に試した方法の内、効果のあった(2)〜(5)の方法を使いましょう。
>>> a = [[1, 0],[0, 1]]
>>> copy_a = a[:] # (2)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[0, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> a = [[1, 0],[0, 1]]
>>> copy_a = a.copy() # (3)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[0, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> a = [[1, 0],[0, 1]]
>>> copy_a = list(a) # (4)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[0, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> a = [[1, 0],[0, 1]]
>>> copy_a = [elem for elem in a] # (5)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[0, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
このように、list aが1次元の場合に有効だった(2)〜(5)の方法では、copy_aの(0,0)成分の値のみを変えることは出来ませんでした。更に以下の方法を試してみます。
>>> a = [[1, 0],[0, 1]]
>>> copy_a = [elem[:] for elem in a] # (5.2)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
False
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
False
>>> a = [[1, 0],[0, 1]]
>>> copy_a = [elem.copy() for elem in a] # (5.3)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
False
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
False
>>> a = [[1, 0],[0, 1]]
>>> copy_a = [list(elem) for elem in a] # (5.4)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
False
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
False
>>> a = [[1, 0],[0, 1]]
>>> copy_a = [[_elem for _elem in elem] for elem in a] # (5.5)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
False
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
False
上の結果から、(5)の方法でlistであるaの要素を呼び出し、その要素に対して(2)〜(4)の方法を用いると無事aの値を変えずにcopy_aの(0,0)成分の値のみを変えられることが分かりました。
2次元ndarrayの複製
2次元listの場合と同様に、2次元ndarray a対しても1次元ndarrayの場合に効果のあった(3)〜(5)の方法で、copy_aの(0,0)成分の値のみを変えられるか試してみます。
>>> a = np.array([[1, 0],[0, 1]])
>>> copy_a = a.copy() # (3)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1 0]
[0 1]]
copy_a = [[0 0]
[0 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> a = np.array([[1, 0],[0, 1]])
>>> copy_a = np.array(a) # (4)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1 0]
[0 1]]
copy_a = [[0 0]
[0 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> a = np.array([[1, 0],[0, 1]])
>>> copy_a = np.array([elem for elem in a]) # (5)
>>> id(copy_a) == id(a)
False
>>> id(copy_a[0]) == id(a[0])
True
>>> id(copy_a[0][0]) == id(a[0][0])
True
>>> copy_a[0][0] = 0
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1 0]
[0 1]]
copy_a = [[0 0]
[0 1]]
>>> id(copy_a[0][0]) == id(a[0][0])
True
2次元listの場合には有効でなかった(3)〜(5)の方法ですが、2次元ndarrayの場合には、aの値を変えずにcopy_aの(0,0)成分の値のみを変えることが出来ました。
実験2
実験2では、0埋めされた配列を作製する幾つかの方法を試しました。
0埋めされたlistの作製
0埋めされた1次元listを作り、第0成分の値を1にする場合、
>>> a = [0 for _ in range(2)] # (1)
>>> id(a[0]) == id(a[1])
True
>>> a[0] = 1
>>> a
[1, 0]
>>> id(a[0]) == id(a[1])
False
>>> a = [0]*2 # (2)
>>> id(a[0]) == id(a[1])
True
>>> a[0] = 1
>>> a
[1, 0]
>>> id(a[0]) == id(a[1])
False
上のように、方法(1)、(2)のどちらで0埋めされたlist aの第0成分の値のみを1にすることが出来ました。
一方で、0埋めされた2次元listの場合は、
>>> a = [[0 for _ in range(2)] for _ in range(2)] # (1.1)
>>> id(a[0]) == id(a[1])
False
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
[[1, 0], [0, 0]]
>>> id(a[0][0]) == id(a[1][0])
False
>>> a = [[0]*2 for _ in range(2)] # (1.2)
>>> id(a[0]) == id(a[1])
False
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
[[1, 0], [0, 0]]
>>> id(a[0][0]) == id(a[1][0])
False
>>> a = [[0]*2]*2 # (2.2)
>>> id(a[0]) == id(a[1])
True
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
[[1, 0], [1, 0]]
>>> id(a[0][0]) == id(a[1][0])
True
のように演算子*
を2回用いて0埋めされた2次元listを作ると、(0,0)成分の値のみを1にすることが出来ません。
0埋めされたndarrayの作製
0埋めされた1次元ndarrayを作り、第0成分の値のみを1に変えるには、方法(1)、(2)で0埋めされた1次元listを作ってndarrayに変換する以外に、np.zeros()
を利用する方法があります。
>>> a = np.zeros(2, int)
>>> id(a[0]) == id(a[1])
True
>>> a[0] = 1
>>> a
array([1, 0])
>>> id(a[0]) == id(a[1])
True
0埋めされた2次元ndarrayを作る場合も、同様にnp.zeros()
を使うことで、(0,0)成分の値のみを1にすることが出来ます。0埋めされた2次元listを作った場合と異なる点は、方法(2.2)を用いても(0,0)成分の値のみを1にすることが出来る点です。
>>> a = np.zeros((2, 2), int)
>>> id(a[0]) == id(a[1])
True
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
array([[1, 0],
[0, 0]])
>>> id(a[0][0]) == id(a[1][0])
True
>>> a = np.array([[0 for _ in range(2)] for _ in range(2)]) # (1.1)
>>> id(a[0]) == id(a[1])
True
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
array([[1, 0],
[0, 0]])
>>> id(a[0][0]) == id(a[1][0])
True
>>> a = np.array([[0]*2 for _ in range(2)]) # (1.2)
>>> id(a[0]) == id(a[1])
True
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
array([[1, 0],
[0, 0]])
>>> id(a[0][0]) == id(a[1][0])
True
>>> a = np.array([[0]*2]*2) # (2.2)
>>> id(a[0]) == id(a[1])
True
>>> id(a[0][0]) == id(a[1][0])
True
>>> a[0][0] = 1
>>> a
array([[1, 0],
[0, 0]])
>>> id(a[0][0]) == id(a[1][0])
True
考察
実験1の結果を見るに、listの複製に関しては、1次元、2次元のいずれにせよ、書き換えたい値を格納しているlistのidが異なれば複製元の配列の各成分の値を変えずに複製した配列の各成分の値を変えることが出来るようです。従って、次のようなことが出来ます。
>>> a = [[1, 0],[0, 1]]
>>> copy_a = a[:] # (2)
>>> id(a) == id(copy_a)
False
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
>>> copy_a[0] = [0, 0]
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1, 0], [0, 1]]
copy_a = [[0, 0], [0, 1]]
>>> id(a[0]) == id(copy_a[0])
False
>>> id(a[0][0]) == id(copy_a[0][0])
False
実験1では、2次元listを方法(2)で複製した場合は複製元のlist aの値を変えずに複製したlist copy_aの各成分の値を変えることが出来ませんでしたが、aとcopy_aのidは異なるため、copy_aの第0要素であるlist copy_a[0]を変えることで、aの各成分の値を変えずにcopy_aの値を変えることが出来ました。
一方でndarrayの場合は、次の例が示すように、listのように単純ではありません。
>>> a = np.array([0, 1])
>>> copy_a0 = a[:] # (2)
>>> copy_a1 = np.array(a) # (4)
>>> id(a) == id(copy_a0), id(a) == id(copy_a1)
(False, False)
>>> id(a[0]) == id(copy_a0[0]), id(a[0]) == id(copy_a1[0])
(True, True)
>>> id(a[1]) == id(copy_a0[1]), id(a[1]) == id(copy_a1[1])
(True, True)
>>> copy_a0[0] = -1
>>> copy_a1[1] = -1
>>> print(f'a = {a}\ncopy_a0 = {copy_a0}\ncopy_a1 = {copy_a1}')
a = [-1 1]
copy_a0 = [-1 1]
copy_a1 = [ 0 -1]
>>>
>>> id(a[0]) == id(copy_a0[0]), id(a[0]) == id(copy_a1[0])
(True, True)
>>> id(a[1]) == id(copy_a0[1]), id(a[1]) == id(copy_a1[1])
(True, True)
idに着目するとaに対するcopy_a0とcopy_a1の条件は等しいのですが、copy_a0のみ、第0成分を変えるとそれがaに反映されてしまいます。実験2で0埋めされたndarray配列を作った際にも見られましたが、idが等しいのに数値が異なるというndarrayの仕様を理解するには、より詳しく調べる必要があります。
備考
実験1の2次元ndarrayの実験では、copy_aの各成分の内1つの値を変える操作をしましたが、例えば、次のように配列を渡してaの値を変えずに、copy_aの各成分の値のみを変えることが出来ます。
>>> a = np.array([[1, 0],[0, 1]])
>>> copy_a = np.array(a) # (4)
>>> id(a) == id(copy_a)
False
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
>>> copy_a[0] = [0, 1]
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1 0]
[0 1]]
copy_a = [[0 1]
[0 1]]
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
>>> a = np.array([[1, 0],[0, 1]])
>>> copy_a = np.array(a) # (4)
>>> id(a) == id(copy_a)
False
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
>>> copy_a[0] = np.array([0, 1])
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1 0]
[0 1]]
copy_a = [[0 1]
[0 1]]
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
>>> a = np.array([[1, 0],[0, 1]])
>>> copy_a = np.array(a) # (4)
>>> id(a) == id(copy_a)
False
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
>>> copy_a[0] = 0, 1
>>> print(f'a = {a}\ncopy_a = {copy_a}')
a = [[1 0]
[0 1]]
copy_a = [[0 1]
[0 1]]
>>> id(a[0]) == id(copy_a[0])
True
>>> id(a[0][0]) == id(copy_a[0][0])
True
実験2の0埋めされたlistを作る実験から、listに演算子*
を作用させることは、次の操作と等しいという直感が得られるでしょう。
# Kは自然数
>>> temp = 0
>>> a = [temp for _ in range(K)]
# a = [0]*K
>>> temp = [0]*K
>>> b = [temp for _ in range(K)]
# b = [[0]*K]*K