Pythonのcopyやdeepcopyでオブジェクトをコピーする方法を解説
Pythonのcopyとは
Pythonのcopyとdeepcopy関数は、どちらもオブジェクトをコピーするときに使います。
copyモジュールをimportすることで利用できます。
copyモジュールはPythonが提供するモジュールの一つで、オブジェクトをコピーしてそれぞれ別のオブジェクトとして保管することができます。
今回はPythonのcopyでオブジェクトをコピーする方法やdeepcopyとの違いについて解説します。
オブジェクトの種類や階層の深さによってはうまくコピーできないこともあるので、こちらについても解説しています。
copy関数の記述方法は下記の通りです。
test1 = test.copy()
コピーしたい値の後ろにcopy関数を記述して利用します。
Pythonでcopyを使用する際にはミュータブル、イミュータブルといったオブジェクトの種類や、参照渡しや値渡しといった引数の渡し方の種類についても理解しておく必要があります。
そこでcopyの使い方の解説に入る前に、まずオブジェクトの種類について解説します。
オブジェクトのミュータブルとイミュータブルについて
Pythonには代入して値を変更できるミュータブルなオブジェクトと、値を変更できないイミュータブルなオブジェクトの2種類が存在します。
オブジェクトの種類 | データの種類 |
---|---|
ミュータブル | list、dict、set、bytearrayなど |
イミュータブル | int、float、str、tuple、bytes、frozensetなど |
ミュータブルなオブジェクトは参照するオブジェクトが共有なので、代入した値を変更することができます。反対にイミュータブルなオブジェクトは参照するオブジェクトが異なります。
ミュータブル
test1 = ['a','b','c']
test2 = test1
test1.append('d')
print (test1)
print (test2)
print('id(test1) = %s' % id(test1))
print('id(test2) = %s' % id(test2))
実行結果
['a', 'b', 'c', 'd']
['a', 'b', 'c', 'd']
#同じオブジェクトを参照している
id(test1) = 22668883604800
id(test2) = 22668883604800
Pythonのオブジェクトにはすべて「オブジェクトID」と呼ばれる固有のIDが割り振られており、これはid関数で確認することができます。
上記の例ではid関数によってオブジェクトIDを調べています。
その結果、オブジェクトIDが同一なので同じオブジェクトを参照しているのが分かります。
イミュータブル
test1 = 10
test2 = test1
print('test1 =',test1)
print('test2 =',test2)
print('test1 = %s' % id(test1))
print('test2 = %s' % id(test2))
test1 = 0
print('test1 =',test1)
print('test2 =',test2)
print('test1 = %s' % id(test1))
print('test2 = %s' % id(test2))
実行結果
test1 = 10
test2 = 10
test1 = 9801536
test2 = 9801536
test1 = 0
test2 = 10
test1 = 9801216
test2 = 9801536
変数test2へ変数test1を代入した段階では、ID「9801536」という同一のオブジェクトが生成された状態です。
この状態でtest1に0を代入してみます。
数字はイミュータブルなオブジェクトなので別のオブジェクトが生成され、test1の値は0となります。
このことはtest1のオブジェクトIDが変化したことから分かります。
test2の方は何も変化していません。
値渡しと参照渡しについて
参照渡しは変数を共有する渡し方で、ミュータブルオブジェクトのように同じオブジェクトを参照するのが特徴です。Pythonでは、変数同士で値を代入すると参照渡しになります。値渡しは変数の値をコピーして渡し、元のオブジェクトは影響を受けません。つまり参照先が異なるのです。
参照渡し
test = ['apple', 'orange']
test1 = test
test[0] = 'peach'
print('test =',test)
print('test1 =',test1)
print('test = %s' % id(test))
print('test1 = %s' % id(test1))
実行結果
test = ['peach', 'orange']
test1 = ['peach', 'orange']
test = 23120715507008
test1 = 23120715507008
変数test1にlist型である変数testを代入し、その後testの中身を書き換えています。
結果としてtest、test1両方のlist内で「apple」という文字列が「peach」という文字列に変化しており、参照しているオブジェクトのIDも同一です。
これを値渡しにしたい場合にcopy関数を使用します。
値渡し
test = ['apple', 'orange']
print('test = %s' % id(test))
test1 = test.copy()
print('test1 = %s' % id(test1))
実行結果
test = 23411624473920
test1 = 23411623595328
ミュータブルオブジェクトである配列が代入された変数にcopy関数を使うことで、新たにオブジェクトが生成されて値渡しができます。
id関数の実行結果でも分かるように、copyを使うことで生成されたオブジェクトは別のものです。
copyの使い方
実際にcopy関数を使って値渡しする方法をサンプルコードで解説します。
import copy #モジュールをインポート
test = ['apple', 'orange']
test1 = test.copy()
test.append('peach')
print('test = ', test)
print('test = %s' % id(test))
print('test1 = ', test1)
print('test1 = %s' % id(test1))
実行結果
test = ['apple', 'orange', 'peach']
test = 22789604275520
test1 = ['apple', 'orange']
test1 = 22789603397056
copy関数を使って生成されたオブジェクトはオブジェクトIDが異なるので、それぞれ別のオブジェクトを参照していることが分かります。
また、オブジェクトが別なので変数testにappend関数で値を付け足してもtest1はその影響を受けていないことが分かります。
copyとdeepcopyの違い
copy関数で正確にコピーできる範囲には限りがあり、2次元配列になると正確にコピーできなくなります。
deepcopyはcopyモジュールに含まれるメソッドのひとつで、名前の通り深い階層のオブジェクトをコピーするために使います。
コピーできる階層が違うため、copyは浅いコピーでdeepcopyは深いコピーと呼ばれています。
deepcopyは、copy関数でコピーできない階層のオブジェクトをコピーしたいときに使います。
deepcopyの使い方
deepcopyの記述方法はcopy関数と同じです。
test1 = test.deepcopy()
copyとdeepcopyの違いについて、多次元配列を例に解説します。
copyの場合
import copy
test = [['a', 'b'], ['c', 'd']]
test1 = test.copy()
test[1].append('e')
print('test = ', test)
print('test = %s' % id(test))
print('test1 = ', test1)
print('test1 = %s' % id(test1))
実行結果
test = [['a', 'b'], ['c', 'd', 'e']]
test = 23037493484800
test1 = [['a', 'b'], ['c', 'd', 'e']]
test1 = 23037493484864
オブジェクトは別ですが変数testに加えた変更がtest1に反映されています。
うまく値渡しできておらず、正しくコピーされたことにはなりません。
deepcopyの場合
import copy
test = [['a', 'b'], ['c', 'd']]
test1 = copy.deepcopy(test)
test[1].append('e')
print('test = ', test)
print('test = %s' % id(test))
print('test1 = ', test1)
print('test1 = %s' % id(test1))
実行結果
test = [['a', 'b'], ['c', 'd', 'e']]
test = 22764581496320
test1 = [['a', 'b'], ['c', 'd']]
test1 = 22764581495680
deepcopyでコピーすると、変数testに加えた文字列「e」が反映されておらず、オブジェクトIDも異なるので正確に値渡しできました。
まとめ
オブジェクトをコピーしたいときは、階層の深さによってcopyとdeepcopy関数を使い分けましょう。配列までの深さであればcopy関数、多次元配列以上の深さであればdeepcopy関数を使うことで正確にコピーできます。
また、Pythonの特徴であるオブジェクトの種類や値の渡し方が混同する方は、下記のようにまとめると覚えやすいでしょう。
- 同じオブジェクトを参照する:ミュータブルなオブジェクト、参照渡し
- オブジェクトが独立している:イミュータブルなオブジェクト、値渡し
Pythonのオブジェクトをコピーしたいときの参考にしてみてください。