Pythonのcopyモジュールの使い方【シャローコピー、ディープコピー】
目次
- Pythonのcopyモジュールの使い方
- シャローコピーとは?
- ディープコピーとは?
- copy.copy()の使い方
- copy.deepcopy()の使い方
- copy()とdeepcopy()の使い分けについて
- おわりに
Pythonのcopyモジュールの使い方
プログラミング言語であるPython(パイソン)にはcopy(コピー)という標準ライブラリがあります。
このcopy
を使うとオブジェクトの「シャローコピー」と「ディープコピー」が可能になります。
シャローコピーは浅いコピー、ディープコピーは深いコピーです。
この記事ではcopy
の使い方を解説します。
具体的には↓を見ていきます。
シャローコピーとは?
ディープコピーとは?
copy.copy()の使い方
copy.deepcopy()の使い方
copy()とdeepcopy()の使い分けについて
シャローコピーとは?
copy
ではシャローコピーとディープコピーを使い分けることが出来ます。
コピーにおける「シャローコピー」とはどういうものなのでしょうか?
コピーとは一方の値をもう一方の値に複製することをさします。
オブジェクトの場合は、一方のオブジェクトをもう一方に複製することです。
シャローコピーは、オブジェクトのコピーにおいて「浅いコピー」を行います。
「浅いコピー」とは「参照によるコピー」と言い換えることも出来ます。
参照とは、あるオブジェクトを間接的に参照することを指します。
C/C++でいうところのポインタがこれにあたります。
シャローコピーはオブジェクトの属性(メンバ変数)などをコピーするときに、この参照を使ってコピーを行います。
つまり、参照の複製です。参照の複製では実体を直接的にコピーすることはしません。
たとえば属性がリストだったとして、シャローコピーはリスト内のデータの再帰的なコピーは行いません。複製するのはリストの参照の複製だけです。
シャローコピーは参照によるコピーなので、コピー元とコピー先のオブジェクトの属性などは両者間で共有されます。
そのため一方のオブジェクトの属性の値を変更すれば、もう一方のオブジェクトの属性も変化します。
シャローコピーは実体の再帰的なコピーを行わないので、速度的にはディープコピーに勝ります。
ディープコピーとは?
いっぽう「ディープコピー」とはどういうものなのでしょうか。
ディープコピーも一方のオブジェクトをもう一方に複製する動作をします。
シャローコピーが参照によるコピーを行うのに対して、ディープコピーは実体の丸ごとのコピーを行います。
つまりオブジェクトがリストなどの属性を持っていたとして、シャローコピーはそのリストのコピーには参照を使いますが、ディープコピーはリストの要素を再帰的にコピーして複製します。
この再帰的なコピーは参照ではなく実体のコピーなので、オブジェクトの深さによっては動作も重くなるしメモリもたくさん使います。
ディープコピーは実体のコピーを丸ごと行うので、元のオブジェクトと複製されたオブジェクトはまったく別のメモリ上に配置されます。
そのためシャローコピーのように一方のオブジェクトの属性を変更したらもう一方のオブジェクトの属性も変化した、などということは起こりません。
この特性のため、ディープコピーはセキュアな処理などに多用される傾向があります。
つまり、オブジェクトが別のオブジェクトの属性の参照を持っていたら不都合が発生するシーンにおいて、ディープコピーは多用されます。
速度的にはシャローコピーに劣りますが、実体のコピーを行うという点でディープコピーはシャローコピーよりセキュアと言えます。
copy.copy()の使い方
copy
モジュールのcopy()
関数はオブジェクトのシャローコピーを行う関数です。
構造的には↓のようになっています。
copy.copy(obj)
copy.copy()
を使うにはcopy
モジュールのインポートが必要です。
copy.copy()
は1つの引数を取り、1つの返り値を返します。
引数にはコピー元のオブジェクトを渡します。
返り値にはシャローコピーされたオブジェクトが返ってきます。
copy.copy()
は↓のように使うことが出来ます。
import copy class OwnerList: def __init__(self): self.owners = ['Bob', 'Taro'] ownerlist = OwnerList() ownerlist_2 = copy.copy(ownerlist)
OwnerList
というクラスを作り、このクラスのオブジェクトをcopy.copy()
でシャローコピーしています。
シャローコピーされた結果はownerlist_2
に代入されています。
シャローコピーはオブジェクトの属性の再帰的な実体のコピーを行わず、参照によるコピーを行うコピーでした。
オブジェクト自体のid
を比較してみます。
ownerlist = OwnerList() print(id(ownerlist)) ownerlist_2 = copy.copy(ownerlist) print(id(ownerlist_2))
↑のコードの出力結果は↓のようになります(結果は環境によって変わります)。
29680688 30806704
↑の結果を見ると、オブジェクト自体のid
は別物になっています。
ちなみにid
の返す値はメモリ上のアドレスを表しています(CPythonの場合)。
問題はオブジェクトの属性のid
です。↓のコードで確認します。
ownerlist = OwnerList() print(id(ownerlist.owners)) ownerlist_2 = copy.copy(ownerlist) print(id(ownerlist_2.owners))
↑のコードの出力は↓のようになります。
12420840 12420840
↑の結果を見ると、オブジェクトの持つ属性(owners
リスト)のid
が共有されていることがわかります。
これがシャローコピーの特徴です。
このようにシャローコピーはオブジェクトの属性を参照でコピーします。
ですので例えば↓のように一方のオブジェクトの属性の値を変更すると、もう一方のオブジェクトの属性にも影響が出ます。
import copy class OwnerList: def __init__(self): self.owners = ['Bob', 'Taro'] ownerlist = OwnerList() ownerlist_2 = copy.copy(ownerlist) # 参照でコピー ownerlist.owners.append('Hanako') # 一方のownersを変更 # 変更が両方のオブジェクトに影響する print(ownerlist.owners) print(ownerlist_2.owners)
↑のコードの出力は↓のようになります。
['Bob', 'Taro', 'Hanako'] ['Bob', 'Taro', 'Hanako']
↑のコードでは一方のオブジェクトのowners
にHanako
という文字列を加えています。
その変更が両方のオブジェクトに影響しているのがわかります。
copy.deepcopy()の使い方
copy
モジュールのdeepcopy()
関数はオブジェクトのディープコピーを行う関数です。
構造的には↓のようになっています。
copy.deepcopy(obj)
copy.deepcopy()
を使うにはcopy
モジュールのインポートが必要です。
copy.deepcopy()
はcopy.copy()
と同様に1つの引数を取り、1つの返り値を返します。
引数にはコピー元のオブジェクトを渡します。
返り値はディープコピーされたオブジェクトです。
copy.deepcopy()
は↓のように使うことが出来ます。
import copy class OwnerList: def __init__(self): self.owners = ['Bob', 'Taro'] ownerlist = OwnerList() ownerlist_2 = copy.deepcopy(ownerlist) # 実体でコピー
deepcopy()
もオブジェクト自体のメモリは新しく確保します。
そのためコピー元とコピー先のオブジェクトのid
は違います。
ownerlist = OwnerList() print(id(ownerlist)) ownerlist_2 = copy.deepcopy(ownerlist) # 参照でコピー print(id(ownerlist_2))
↑のコードの出力は↓のようになります(環境によって変わります)。
25617456 26744072
問題はオブジェクトの属性ですが、↓のコードでオブジェクトの属性owners
のid
を確認します。
ownerlist = OwnerList() ownerlist_2 = copy.deepcopy(ownerlist) # 参照でコピー print(id(ownerlist.owners)) print(id(ownerlist_2.owners))
↑のコードの出力は↓のようになります。
20481768 20642632
↑の結果を見るとそれぞれのオブジェクトの持つowners
と言う属性のid
が違っているのがわかります。
このようにディープコピーはオブジェクトの持つ属性の実体を再帰的にコピーします。
そのため属性はメモリ上では共有されず、それぞれ別のメモリとして配置されます。
copy()とdeepcopy()の使い分けについて
copy()
とdeepcopy()
の大きな違いは、メモリ上で共有されるかされないかの違いです。
つまり参照を持っているか持っていないかの違いと言えます。
このためオブジェクトの参照が欲しければcopy()
を使い、それ以外はdeepcopy()
を使うという使い分けができます。
参照は一方の変更がもう一方に対して影響を与えるという特徴を持っています。
このため、意図しない動作になることが多々あります。
よってオブジェクトに対して何か書き込みの処理を行う場合は、この参照の振る舞いについて考慮しておく必要があります。
逆に読み込みの処理だけを行う場合は参照で十分と言えます。なぜなら値を変更しないからです。
(もちろん読み取り中に値が変更されたら困る場合はディープコピーの方が適しています)
deepcopy()
は実体のコピーを行うので、動作は少し遅くなりますが、その分セキュアなコピーが可能です。
コピーされたオブジェクトを変更しても、その変更が他のオブジェクトに波及するということがありません。
この2つのコピーの特性を把握して使い分けることでプログラムの品質を上げることが出来ると思います。
おわりに
今回はcopy
モジュールの使い方を解説しました。
コピーはプログラミングで最も使う機能と言ってもいいですが、けっこう奥が深いものでした。
(^ _ ^) | コピーの道は長い |