web-dev-qa-db-ja.com

整数のリストのディープコピーがメモリに同じ整数を返すのはなぜですか?

クラスで学んだ浅いコピーと深いコピーの違いを理解しています。ただし、以下は意味がありません

_import copy

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

b = copy.deepcopy(a)

print(a is b)
print(a[0] is b[0])
----------------------------
~Output~
>False
>True
----------------------------
_

オブジェクトとその構成要素がディープコピーの別のメモリロケーションに再作成されているため、print(a[0] is b[0])はFalseに評価されませんか?クラスでこれを話し合ったので、私はこれをテストしてみましたが、うまくいかないようです。

18
Gerald Lee

これは、Pythonが小さな整数のオブジェクトをインターン化しているためと思われます。このステートメントは正しいですが、その動作の原因ではありません。

大きな整数を使用するとどうなるかを見てみましょう。

> from copy import deepcopy
> x = 1000
> x is deepcopy(x)
True

copyモジュールを掘り下げると、アトミック値でdeepcopyを呼び出すと、関数の呼び出しが遅延することがわかります _deepcopy_atomic

def _deepcopy_atomic(x, memo):
    return x

つまり、実際に起こっていることは、deepcopyがアトミック値をコピーせず、それだけを返すということです。

例として、これはintfloatstrfunctionなどの場合です。

17

この動作の理由は、Pythonが最適化されて小さな整数が実際に別のメモリロケーションにないようにするためです。id of 1をチェックしてください。これらは常に同じ:

>>> x = 1
>>> y = 1
>>> id(x)
1353557072
>>> id(y)
1353557072

>>> a = [1, 2, 3, 4, 5]
>>> id(a[0])
1353557072

>>> import copy
>>> b = copy.deepcopy(a)
>>> id(b[0])
1353557072

整数オブジェクト からの参照:

現在の実装では、-5256の間のすべての整数の整数オブジェクトの配列を保持しています。その範囲でintを作成すると、実際には既存のオブジェクトへの参照が返されます。したがって、1の値を変更できるはずです。 Pythonは未定義です。:-)

10
Yu Hao

OlivierMelançonの答えは、deepcopy関数呼び出しが、同じintオブジェクトへの参照を、それらのコピーではなく、どのようにして返すかという機械的な質問だとしたら、正しい答えです。私は一歩下がって、なぜdeepcopyが賢明なことなのかという質問に答えます。

データ構造のコピー(深いコピーまたは浅いコピー)を作成する必要があるのは、元の状態に影響を与えずに内容を変更できるようにするためです。または、古い状態のコピーを保持したままoriginalを変更できます。データ構造にそれ自体が変更可能なネストされた部分がある場合、その目的のためにディープコピーが必要です。 [[1, 2], [3, 4]]のように、2Dグリッドのすべての数値を乗算する次の例を考えてみます。

import copy

def multiply_grid(grid, k):
    new_grid = copy.deepcopy(grid)

    for row in new_grid:
        for i in range(len(row)):
            row[i] *= k

    return new_grid

リストなどのオブジェクトは変更可能であるため、操作row[i] *= kは状態を変更します。リストのコピーを作成することは、変異を防ぐ方法です。外部リストと内部リスト(つまり行)の両方のコピーを作成するには、ここでもディープコピーが必要です。これらも変更可能です。

ただし、整数や文字列などのオブジェクトは不変であるため、状態を変更することはできません。 intオブジェクトが13の場合、kを掛けても13のままです。乗算の結果、別のintオブジェクトが生成されます。防御すべき変異はないため、コピーを作成する必要はありません。

興味深いことに、deepcopyは、コンポーネントがすべて不変である場合はタプルのコピーを作成しませんが、変更可能なコンポーネントがある場合はタプルのコピーを作成します。

>>> import copy
>>> x = ([1, 2], [3, 4])
>>> x is copy.deepcopy(x)
False
>>> y = (1, 2)
>>> y is copy.deepcopy(y)
True

ロジックは同じです。オブジェクトが不変であるが、変更可能なネストされたコンポーネントがある場合、元のコンポーネントへの変更を避けるためにコピーが必要です。しかし、構造全体が完全に不変である場合、防御するための変異はなく、したがってコピーは必要ありません。

2
kaya3