Pythonオブジェクトの浅いコピーとディープコピー

Pythonオブジェクトの浅いコピーとディープコピー

オブジェクトのhttps://realpython.com/python-variables/#variable-assignment[Pythonの割り当てステートメントはコピーを作成しません]、名前のみをオブジェクトにバインドします。 不変オブジェクトの場合、通常は違いはありません。

しかし、可変オブジェクトまたは可変オブジェクトのコレクションを操作するには、これらのオブジェクトの「実際のコピー」または「クローン」を作成する方法を探しているかもしれません。

基本的に、時にはオリジナルを自動的に修正することなく修正できるコピーが必要になることがあります。 この記事では、Python 3でオブジェクトをコピーまたは「クローン」する方法の概要と、いくつかの注意事項を説明します。

_ *注意:*このチュートリアルはPython 3を念頭に置いて書かれていますが、オブジェクトのコピーに関してはPython 2と3にほとんど違いはありません。 違いがある場合は、本文でそれらを指摘します。 _

Pythonの組み込みコレクションをコピーする方法を見てみましょう。 lists、dicts、setsのようなPythonの組み込みの可変コレクションは、既存のコレクションでファクトリ関数を呼び出すことでコピーできます。

new_list = list(original_list)
new_dict = dict(original_dict)
new_set = set(original_set)

ただし、このメソッドはカスタムオブジェクトでは機能せず、その上、_shallowコピー_のみを作成します。 リスト、辞書、セットなどの複合オブジェクトの場合、_shallow_と_deep_のコピーには重要な違いがあります。

  • *浅いコピー*は、新しいコレクションオブジェクトを作成し、元のコレクションオブジェクトにある子オブジェクトへの参照をそのコレクションオブジェクトに追加することを意味します。 本質的に、浅いコピーは_1レベルの深さ_です。 コピープロセスは再帰しないため、子オブジェクト自体のコピーは作成されません。

  • *ディープコピー*は、コピープロセスを再帰的にします。 最初に新しいコレクションオブジェクトを構築してから、元のコレクションオブジェクトで見つかった子オブジェクトのコピーを再帰的に格納することを意味します。 この方法でオブジェクトをコピーすると、オブジェクトツリー全体をたどって、元のオブジェクトとそのすべての子の完全に独立したクローンを作成します。

ちょっと一口でした。 ディープコピーとシャローコピーの違いを理解するための例をいくつか見てみましょう。

*無料ボーナス:*リンク:[Python Tricks:The Book]の章にアクセスするには、ここをクリックして、Pythonのベストプラクティスを簡単な例とともに示します。すぐに適用して、より美しい+ Pythonコードを記述できます。

浅いコピーの作成

以下の例では、新しいネストされたリストを作成し、 `+ list()+`ファクトリー関数で_shallowly_コピーします:

>>>

>>> xs = [[ys = list(xs)  # Make a shallow copy

これは、「+ ys 」が「 xs +」と同じ内容の新しい独立したオブジェクトになることを意味します。 これを確認するには、両方のオブジェクトを調べます。

>>>

>>> xs
[[ys
[[To confirm `+ys+` really is independent from the original, let’s devise a little experiment. You could try and add a new sublist to the original (`+xs+`) and then check to make sure this modification didn’t affect the copy (`+ys+`):

[.repl-toggle]#>>>#

[source,python,repl]

>>> xs.append(['new sublist'])>>> xs [[new sublist']] >>> ys [[ご覧の通り、これは期待した効果がありました。 コピーしたリストを「表面的な」レベルで変更しても、まったく問題ありませんでした。

ただし、元のリストの_shallow_コピーのみを作成したため、「+ ys 」には「 xs +」に保存されている元の子オブジェクトへの参照が含まれています。

これらの子はコピーされませんでした。 それらはコピーされたリストで再び参照されただけです。

したがって、 `+ xs `の子オブジェクトの1つを変更すると、この変更は ` ys +`にも反映されます。これは、_両方のリストが同じ子オブジェクト_を共有しているためです。 コピーは、1レベルの浅いコピーのみです。

>>>

>>> xs[1][0] = 'X'
>>> xs
[[X', 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[X', 5, 6], [7, 8, 9]]

上記の例では(一見) `+ xs `に変更を加えただけです。 しかし、 ` xs `のインデックス1の_both_サブリスト_and_ ` ys +`が変更されたことがわかります。 繰り返しますが、これは元のリストの_shallow_コピーのみを作成したために発生しました。

最初のステップで「+ xs +」の_deep_コピーを作成していた場合、両方のオブジェクトは完全に独立しています。 これは、オブジェクトの浅いコピーと深いコピーの実際の違いです。

これで、組み込みコレクションクラスの一部の浅いコピーを作成する方法がわかり、浅いコピーと深いコピーの違いがわかりました。 まだ回答が必要な質問は次のとおりです。

  • ビルトインコレクションのディープコピーを作成するにはどうすればよいですか?

  • カスタムクラスを含む任意のオブジェクトのコピー(浅いものと深いもの)を作成するにはどうすればよいですか?

これらの質問に対する答えは、Python標準ライブラリの `+ copy +`モジュールにあります。 このモジュールは、任意のPythonオブジェクトの浅いコピーと深いコピーを作成するためのシンプルなインターフェースを提供します。

ディープコピーの作成

前のリストコピーの例を繰り返しますが、1つの重要な違いがあります。 今回は、代わりに `+ copy `モジュールで定義された ` deepcopy()+`関数を使用して_deep_コピーを作成します。

>>>

>>> import copy
>>> xs = [[zs = copy.deepcopy(xs)

`+ copy.deepcopy()`で作成した ` xs `とそのクローン ` zs +`を調べると、前の例のように両方が同じに見えることがわかります。

>>>

>>> xs
[[zs
[[However, if you make a modification to one of the child objects in the original object (`+xs+`), you’ll see that this modification won’t affect the deep copy (`+zs+`).

Both objects, the original and the copy, are fully independent this time. `+xs+` was cloned recursively, including all of its child objects:

[.repl-toggle]#>>>#

[source,python,repl]

>>> xs [1] [0] = 'X' >>> xs [[X', 5, 6], [7, 8, 9]] >>> zs [[Pythonインタプリタと一緒に座ってこれらの例を今すぐ再生するのに少し時間がかかるかもしれません。 オブジェクトをコピーして頭を包み込むのは、サンプルを実際に体験して遊んでみると簡単です。

ところで、 `+ copy `モジュールの関数を使用して浅いコピーを作成することもできます。 ` copy.copy()+`関数はオブジェクトの浅いコピーを作成します。

これは、コードのどこかに浅いコピーを作成していることを明確に伝える必要がある場合に役立ちます。 `+ copy.copy()+`を使用すると、この事実を示すことができます。 ただし、ビルトインコレクションの場合、リスト、ディクテーション、およびファクトリ関数のセットを使用して浅いコピーを作成する方が、よりPythonicと見なされます。

任意のPythonオブジェクトのコピー

まだ答えが必要な質問は、カスタムクラスを含む任意のオブジェクトのコピー(浅く深い)をどのように作成するかです。 今それを見てみましょう。

繰り返しになりますが、 `+ copy `モジュールが役に立ちます。 その ` copy.copy()`および ` copy.deepcopy()+`関数は、任意のオブジェクトを複製するために使用できます。

繰り返しますが、これらの使用方法を理解する最良の方法は、簡単な実験を行うことです。 これは、前のリストコピーの例に基づいて行います。 簡単な2Dポイントクラスを定義することから始めましょう。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x!r}, {self.y!r})'

これが非常に簡単だったことに同意していただければ幸いです。 Pythonインタープリターでこのクラスから作成されたオブジェクトを簡単に検査できるように、 `+ repr ()+`実装を追加しました。

_ *注意:*上記の例では、https://dbader.org/blog/python-string-formatting [Python 3.6 f-string]を使用して、 `+ repr _ +`によって返される文字列を作成します。 Python 2および3.6より前のPython 3のバージョンでは、たとえば次のような異なる文字列フォーマット式を使用します。

def __repr__(self):
    return 'Point(%r, %r)' % (self.x, self.y)

_ _

次に、 `+ Point `インスタンスを作成し、 ` copy +`モジュールを使用して(浅く)コピーします:

>>>

>>> a = Point(23, 42)
>>> b = copy.copy(a)

元の `+ Point +`オブジェクトとその(浅い)クローンの内容を調べると、予想されるものがわかります。

>>>

>>> a
Point(23, 42)
>>> b
Point(23, 42)
>>> a is b
False

ここで留意すべきことは他にもあります。 ポイントオブジェクトはその座標に不変の型(int)を使用するため、この場合、浅いコピーと深いコピーの間に違いはありません。 しかし、私はすぐに例を拡張します。

さらに複雑な例に移りましょう。 2D長方形を表す別のクラスを定義します。 より複雑なオブジェクト階層を作成できるようにそれを行います。私の長方形は `+ Point +`オブジェクトを使用して座標を表します。

class Rectangle:
    def __init__(self, topleft, bottomright):
        self.topleft = topleft
        self.bottomright = bottomright

    def __repr__(self):
        return (f'Rectangle({self.topleft!r}, '
                f'{self.bottomright!r})')

繰り返しますが、まず、四角形インスタンスの浅いコピーを作成しようとします。

rect = Rectangle(Point(0, 1), Point(5, 6))
srect = copy.copy(rect)

元の長方形とそのコピーを調べると、 `+ repr ()+`オーバーライドがうまく機能し、シャローコピープロセスが期待どおりに機能したことがわかります。

>>>

>>> rect
Rectangle(Point(0, 1), Point(5, 6))
>>> srect
Rectangle(Point(0, 1), Point(5, 6))
>>> rect is srect
False

前のリストの例がディープコピーとシャローコピーの違いをどのように示していたか覚えていますか? ここでも同じアプローチを使用します。 オブジェクトをオブジェクト階層のより深いところで変更すると、この変更が(浅い)コピーにも反映されます:

>>>

>>> rect.topleft.x = 999
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

これがあなたが期待したとおりに動作したことを願っています。 次に、元の四角形のディープコピーを作成します。 その後、別の変更を適用し、影響を受けるオブジェクトを確認します。

>>>

>>> drect = copy.deepcopy(srect)
>>> drect.topleft.x = 222
>>> drect
Rectangle(Point(222, 1), Point(5, 6))
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

ほら! 今回は、ディープコピー( + drect +)はオリジナル( + rect +)およびシャローコピー( + srect +)から完全に独立しています。

ここでは多くのことを説明しましたが、オブジェクトのコピーにはさらに細かいポイントがあります。

このトピックを深く掘り下げる(ha!)ことは有益です。そのため、https://docs.python.org/3/library/copy.html [`+ copy `モジュールドキュメント]を調べてください。 たとえば、オブジェクトで特殊なメソッド ` copy ()`と ` deepcopy ()+`を定義することで、オブジェクトのコピー方法を制御できます。

覚えておくべき3つのこと

  • オブジェクトの浅いコピーを作成しても、子オブジェクトは複製されません。 したがって、コピーは元のコピーから完全に独立しているわけではありません。

  • オブジェクトのディープコピーは、子オブジェクトを再帰的に複製します。 クローンはオリジナルから完全に独立していますが、ディープコピーの作成は遅くなります。

  • `+ copy +`モジュールで任意のオブジェクト(カスタムクラスを含む)をコピーできます。

他の中級レベルのPythonプログラミング手法をさらに詳しく知りたい場合は、次の無料ボーナスをご覧ください。

*無料ボーナス:*リンク:[Python Tricks:The Book]の章にアクセスするには、ここをクリックして、Pythonのベストプラクティスを簡単な例とともに示します。すぐに適用して、より美しい+ Pythonコードを記述できます。