【ITニュース解説】Shallow copy & Deep copy in Python (11)
2025年10月01日に「Dev.to」が公開したITニュース「Shallow copy & Deep copy in Python (11)」について初心者にもわかりやすく解説しています。
ITニュース概要
Pythonでデータを複製するシャローコピーとディープコピーの挙動を解説。リストや辞書など多様なデータ型を例に、特にbytearrayにおいて、代入・シャローコピー・ディープコピーそれぞれで、複製されたデータが元のデータとどのように独立するかを詳細に示している。
ITニュース解説
システムエンジニアを目指す上で、プログラムがメモリ上でデータをどのように扱うかを理解することは、堅牢なシステムを構築するための基礎となる。特にPythonのような高水準言語では、変数がデータそのものではなく、メモリ上のデータへの「参照」を保持するという特性があるため、データのコピー方法を正しく理解することが極めて重要である。ここでは、データのコピーに関する基本的な概念である「代入」「シャローコピー」「ディープコピー」について、特にPythonのbytearray型を例に詳細に解説する。bytearrayは、変更可能なバイト列を扱うデータ型であり、そのコピーの挙動は他のデータ型にも通じる重要な考え方を含んでいる。
まず、変数とメモリ上のデータの関係についてだが、Pythonにおいて変数名は、メモリ上に存在する実際のデータ(オブジェクト)へのラベルやポインタのような役割を果たす。つまり、v1 = bytearray(b'abcde')というコードを実行すると、b'abcde'というバイト列がメモリ上のどこかに作成され、そのデータへの参照が変数v1に格納される。
次に、このbytearrayを別の変数に「代入」するケースを考える。例えば、v2 = v1という代入操作を行うと、v2はv1が参照しているのと同じbytearrayオブジェクトを指すようになる。このとき、メモリ上には新しいbytearrayオブジェクトは作成されず、v1とv2は全く同じデータ、つまり同じバイト列を共有している状態となる。これをv1 is v2という式で確認するとTrueと表示され、両者が同一のオブジェクトであることを示している。bytearrayはミュータブル(変更可能)なデータ型であるため、この状態でv2の要素を変更すると、例えばv2[1] = ord('X')のように値を変更すると、v1の内容も同時に変更されることになる。結果として、v1もv2もbytearray(b'aXcYe')という値になるだろう。また、v1[2] is v2[2]と確認してもTrueとなり、個々のバイト値も同じメモリ上のオブジェクトを参照していることが分かる。
次に、「シャローコピー(浅いコピー)」について説明する。シャローコピーは、元のオブジェクトとは別の新しいオブジェクトを作成するが、その新しいオブジェクトが持つ内部の要素は、元のオブジェクトの要素と「共有」される場合がある。しかし、bytearrayに関しては少し特殊な挙動を示す。bytearrayのシャローコピーは、新しいbytearrayオブジェクトを作成し、元のbytearrayが保持するすべてのバイト値を新しいオブジェクトにコピーする。このため、v1とv2は全く異なるbytearrayオブジェクトとなる。v1 is v2と確認するとFalseと表示され、両者が独立したオブジェクトであることが示される。しかし、v1[2] is v2[2]と確認するとTrueと表示されることがある。これは、bytearrayの個々の要素であるバイト値(0から255までの整数)がイミュータブルな値であり、Pythonの内部で小さな整数オブジェクトがキャッシュされ、同じ値であれば同じオブジェクトを参照することがあるためだ。重要なのは、v2のバイト値を変更しても、例えばv2[1] = ord('X')のように変更しても、v1のバイト列には影響を与えないという点である。これは、シャローコピーによってbytearrayオブジェクト自体が複製され、その中に格納されているバイト列も独立して存在しているためだ。シャローコピーを実行する方法としては、bytearray.copy()メソッド、copyモジュールのcopy.copy()関数、スライシング(v1[:])、そしてbytearray()コンストラクタに元のbytearrayを渡す方法(bytearray(v1))などがある。これらすべての方法で、v1はbytearray(b'abcde')のまま保持され、v2はbytearray(b'aXcYe')となる。
最後に、「ディープコピー(深いコピー)」について解説する。ディープコピーは、オブジェクトとその内部に存在するすべての要素を再帰的にコピーし、元のオブジェクトから完全に独立した新しいオブジェクトを作成する操作である。これは、オブジェクトがさらに複雑なオブジェクトを内部に持っている場合に、それらすべてを複製して、元のデータとの依存関係を完全に断ち切るために用いられる。Pythonでは、通常copyモジュールのcopy.deepcopy()関数を使用してディープコピーを行う。しかし、bytearrayの場合、その要素は0から255までの単純な整数値であり、それ自体がさらにミュータブルな複雑なオブジェクトではない。そのため、bytearrayに対してディープコピーを行っても、実質的にはシャローコピーと同じ挙動を示す。v1とv2は独立したbytearrayオブジェクトとなり、v1 is v2はFalseと表示される。v2のバイト値を変更してもv1には影響せず、v1[2] is v2[2]もTrueとなる。つまり、bytearrayにおいては、シャローコピーもディープコピーも、結果として元のbytearrayとは完全に独立した新しいbytearrayオブジェクトとそのバイト列を生成することになる。
このように、Pythonにおけるbytearrayのコピーは、一見複雑に見えるが、変数とメモリ上のデータの関係、そしてミュータブルなオブジェクトの挙動を理解すれば、適切に扱うことができる。データの独立性を確保するために、代入とコピーの違いを常に意識し、プログラムの意図に合った方法を選択することが、システム開発の品質を高める上で不可欠となる。bytearrayのような、要素が単純なイミュータブルな値で構成されるミュータブルなシーケンスの場合、シャローコピーとディープコピーは結果として同じように「元のオブジェクトから独立した複製」を生成するという点を把握することが重要である。