この記事では、Pythonでオブジェクトをコピーする方法を解説します。
辞書やリストなどのオブジェクトを使用する場合、元のオブジェクトに影響を及ぼさないようにコピーを生成したくなる場合があります。
それでは、コピーの方法を見ていきましょう!
コピーの必要性
Pythonでは、辞書やシーケンスなどのミュータブルなオブジェクトを他の変数に代入して使用すると元のオブジェクトにも影響を与えてしまう。
l = [1, 2, 3]
new_l = l
new_l.append(10)
print(f'l: {l}, new_l: {new_l}')
実行結果
l: [1, 2, 3, 10], new_l: [1, 2, 3, 10]
関数の引数として渡す場合にも同様のことが起こる。
l = [1, 2, 3]
def func(l: list):
l.append(10)
func(l)
print(l)
実行結果
[1, 2, 3, 10]
このように、「ミュータブルなコレクション」または「ミュータブルなアイテムを含むコレクション」は代入して使用すると元のオブジェクトまで変わってしまう恐れがある。
なので、元のオブジェクトが変更されたら困る場合には、コピーを渡すことで元のオブジェクトに影響を及ぼさないように同じ値のオブジェクトを生成することができます。
辞書やシーケンスのコピー
辞書やシーケンスには簡単にコピーが生成できるようにcopy
メソッドが定義されています。
l = [1, 2, 3]
# コピーを渡す
new_l = l.copy()
new_l.append(10)
print(f'l: {l}, new_l: {new_l}')
実行結果
l: [1, 2, 3], new_l: [1, 2, 3, 10]
関数の引数にもコピーを渡すことで関数内で書き換えても元のオブジェクトに影響を及ぼさない。
l = [1, 2, 3]
def func(l: list):
l.append(10)
# コピーを渡す
func(l.copy())
print(l)
実行結果
[1, 2, 3]
シーケンスの場合は、スライスを使ってコピーすることもできます。
l = [1, 2, 3]
# スライスでコピー
new_l = l[:]
new_l.append(10)
print(f'l: {l}, new_l: {new_l}')
実行結果
l: [1, 2, 3], new_l: [1, 2, 3, 10]
しかし、ここで紹介したコピーでは「ミュータブルなアイテムを含むコレクション」にまで対応することはできない。
浅いコピー
先ほど紹介したコピー方法では、例えばリスト内にリストが格納されている場合にコピーを生成したとしても格納されたリストは参照を挿入してしまいます。
# リストを格納したリスト
l = [1, 2, [10, 11]]
# コピー生成
ll = l.copy()
# コピーのネストされたリストに値を追加
ll[2].append(100)
print(l)
実行結果
[1, 2, [10, 11, 100]]
このようなコピー方法を「浅いコピー」と呼びます。
浅いコピーは、copy
モジュールのcopy
関数を使うことでも生成できます。
import copy
l = [1, 2, 3]
# コピーを渡す
new_l = copy.copy(l)
new_l.append(10)
print(f'l: {l}, new_l: {new_l}')
実行結果
l: [1, 2, 3], new_l: [1, 2, 3, 10]
copy
メソッドが実装されていないオブジェクト(ユーザー定義クラスなど)で役立ちます。
深いコピー
ネストされたオブジェクトにもコピーを渡して欲しい場合は、copy
モジュールのdeepcopy
関数を使って「深いコピー」を実行する必要があります。
import copy
# リストを格納したリスト
l = [1, 2, [10, 11]]
# コピー生成
ll = copy.deepcopy(l)
# コピー内に格納されたリストに値を追加
ll[2].append(100)
print(l)
実行結果
[1, 2, [10, 11]]
深いコピーでは以下のような問題が発生する恐れがあるので注意してください。
- 再帰的なオブジェクト (直接、間接に関わらず、自分自身に対する参照を持つ複合オブジェクト) は再帰ループを引き起こします。
- 深いコピーは何もかもコピーしてしまうため、例えば複数のコピー間で共有するつもりだったデータも余分にコピーしてしまいます。
まとめ
この記事では、Pythonでオブジェクトをコピーする方法を解説しました。
コピーには、オブジェクトのサイズに比例してそれなりのコストがかかります。なので、本当に必要な場合のみに留めておきましょう!
それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ