Python のコピー, copy ってなに?
Python のコピーには2種類あります。1つは「浅いコピー」。もう1つは「深いコピー」です。 「浅いコピー」も「深いコピー」も他のプログラミング言語でも使われる言葉なので 覚えておいてそんはないかなと思います。
では「浅いコピー」も「深いコピー」とは、どういう意味でしょうか?
浅いコピー | ちょっとだけコピー |
深いコピー | 全部コピー |
何を言っているのか、全くわかりません。そこでスライドを作りました。まず identity とは何かについて、簡単にイメージ説明して、そのあと
copy と deepcopy の違いを説明しています。
しかし、なぜ2種類あるのでしょうか?
それは Python のオブジェクトの構造と関係があります。
オブジェクトは属性を持っています。そしてその属性には別のオブジェクトが代入されています。 その別のオブジェクトは属性を持っていて、さらにその属性には別の別のオブジェクトが代入されています。
一体、どこまでコピーすればいいのでしょうか? それはきっとその時と場合によると思いますが、提供されている機能は2つに大別されます。
変数に代入された名前空間だけ生成するコピーを「浅いコピー」と呼びます。 変数とその配下の属性全ての名前空間を生成するコピーを「深いコピー」と呼びます。
しかし「深いコピー」の全てとはなんでしょうか? 全てのオブジェクトは属性を持ちます。コピーが止まることがありません。
Python の「深いコピー」はイミュータブルな名前空間は、コピーせず、そこで動作を止めてしまいます。 ちなみにリスト list, 集合 set, 辞書 dict は copy メソッドを持っていますが、このコピーは浅いコピーです。
実際に触って動作を確認してみる。
オブジェクトが持っている identity を全て表示する ids という関数を作って、実際にどのようにコピーされているかを確認して見ました。identity がどのように変化するでしょうか。
def sample_code(): pc = Computer( Cpu('2.3GHz', 5), Memory('8GB', '2133MHz', 'DDR4'), Ssd('256GB')) print('# pc') pprint.pprint(ids(pc)) print('# copy.copy(pc)') print('# copy.copy creates only one instance\n' '# contained in variable.') pprint.pprint(ids(copy.copy(pc))) print('# copy.deepcopy(pc)') print('# copy.deepcopy creates all intances\n' '# contained in variables and attributes except immutable objects') pprint.pprint(ids(copy.deepcopy(pc)))
$ python ids.py # pc (4510800024, {'auxiliary_memory': (4510185176, {'volume': 4510149464}), 'cpu': (4510150328, {'clock': 4509278648, 'core': 4507450848}), 'primary_memory': (4510150384, {'clock': 4510076408, 'type_': 4510076464, 'volume': 4510076352})}) # copy.copy(pc) # copy.copy creates only one instance # contained in variable. (4510800080, {'auxiliary_memory': (4510185176, {'volume': 4510149464}), 'cpu': (4510150328, {'clock': 4509278648, 'core': 4507450848}), 'primary_memory': (4510150384, {'clock': 4510076408, 'type_': 4510076464, 'volume': 4510076352})}) # copy.deepcopy(pc) # copy.deepcopy creates all intances # contained in variables and attributes except immutable objects (4510800080, {'auxiliary_memory': (4511027720, {'volume': 4510149464}), 'cpu': (4510820392, {'clock': 4509278648, 'core': 4507450848}), 'primary_memory': (4510872856, {'clock': 4510076408, 'type_': 4510076464, 'volume': 4510076352})}) $
copy, deepcopy 関数は、str や int などの immutable なオブジェクトは、singleton として取り扱い、インスタンス化しません。
def _copy_immutable(x): return x
まったく関係ないですが CPython では int は - 5 以上 256 以下の値を singleton として扱っている気配があります。例えば
- 5 以上 256 以下では a == b なら a is b となります。
それ以外では a == b であっても a is not b になります。
>>> # - 5 以上 256 以下以外の整数のリストが返されます。 >>> [a for a, b in zip(range(-10, 262), range(-10, 262)) if a is not b] [-10, -9, -8, -7, -6, 257, 258, 259, 260, 261] >>>
例えば、 a = 1; b = 1 とすると、 a と b は値 1 を持つ同じオブジェクトを参照するときもあるし、 そうでないときもあります。これは "実装に依存" します。
3.1. オブジェクト、値、および型
copy メソッドと copy 関数
さて2つの違いは何でしょうか?
import copy copy(list(range(3))) list(range(3)).copy()
関数は、メソッドを呼び出しているだけです。
# Step 1 copy(list(range(3))) # Step 2 list(range(3)).copy()
list, dict, set には専用の copy メソッドがあります。
list, dict, set オブジェクトを関数で copy するときは、
単純にそのメソッドを呼び出しています。
d[list] = list.copy d[dict] = dict.copy d[set] = set.copy d[bytearray] = bytearray.copy
このようにして copy という処理については、関数で呼び出す方法とメソッドで呼び出す方法が、混在してしまっています。
これが公式サイトの FAQ に記載されていた "粗探しのしようが" あるところかなと思ったりもします。
個々のケースについては粗探しのしようがありますが、Python の一部であるし、根本的な変更をするには遅すぎます。
Python にメソッドを使う機能 (list.index() 等) と関数を使う機能 (len(list) 等) があるのはなぜですか?
これを解消するには各クラスで __copy__ メソッドを実装する必要があるかなと思います。確かに "根本的な変更をする" のは、結構難しそうですね。