Python3の世界では数値や文字列も含めて全てオブジェクトとして扱われますが、オブジェクトをコピーする方法はデータの型によって様々です。そこで本記事ではPython3におけるオブジェクトのコピー方法をデータ型毎に整理しようと思います。なお本記事で扱うデータ型は「文字列」「数値」「タプル」「リスト」「辞書」「集合」の6種類です。
データ毎にまとめる前に、代入操作(「=」)はミュータブルとイミュータブルなオブジェクトで挙動が変わる事を先にお伝えしておきます。ミュータブルとは同一オブジェクトに対して後から値を書き換える事が出来るオブジェクトの事で、逆にイミュータブルとは後から同一オブジェクトに対して値を書き換える事が出来ないオブジェクトの事です。
ミュータブル
- リスト
- 辞書
- 集合
イミュータブル
- 文字列
- 数値
- タプル
以下の例で代入操作における挙動の差を具体的に確認してみます。ミュータブルなリストでは、要素を変更した後もオブジェクトのidは変わらず同一オブジェクトに対して変更が加えられている事が分かります。それに対して、イミュータブルな数値を変更するとオブジェクトのidが変わっています。すなわち同一オブジェクトは書き換えられないので新しくオブジェクトが作成されています。
▼リストの例
>>> a = [1, 2, 3] >>> id(a) 4349712392 >>> a[0] = 5 >>> a [5, 2, 3] >>> id(a) 4349712392
▼数値の例
>>> a = 3 >>> id(a) 4304841280 >>> a = 5 >>> id(a) 4304841344
文字列/数値/タプルのコピー
イミュータブルな文字列、数値、タプルのコピーについては「=」の代入をコピーと捉える事が出来ます。以下の例では「y = x」とした後、xもyも同じidとなっていて同じ値を参照しています。しかしイミュータブルなデータ型は上記で説明した通り同一オブジェクトに対して値を変える事は出来ないため再代入すると別のidでオブジェクトが作り直されます。実際、以下の例ではxに5を再代入してもyは3のままです。そしてxのidは変わっているのが見て取れます。このようにイミュータブルのデータ型は「=」で代入しても一方の変数が他方の変数の値に影響を与えるという事はないので実質的にはコピーと同一と捉える事も出来るという訳です。
>>> x = 3 >>> y = x >>> id(x) 4304841280 >>> id(y) 4304841280 >>> x = 5 >>> x 5 >>> y 3 >>> id(x) 4304841344 >>> id(y) 4304841280
リストのコピー
リストのようなミュータブルなオブジェクトの場合「=」による代入だと一方の変数の変更が他方の変数の値にも影響します。以下の例ではxの要素を書き換えるとyの要素も書き換わっています。
>>> x = ['a', 'b', 'c']
>>> y = x
>>> x
['a', 'b', 'c']
>>> y
['a', 'b', 'c']
>>> x[0] = 'd' # コピー元の要素を変更
>>> x
['d', 'b', 'c']
>>> y
['d', 'b', 'c'] # コピー先の要素も変更されてしまっている!!
そこでコピー元とコピー先で相互に影響しないリストのコピー方法として以下のやり方が用意されています。これらの方法でコピーするとコピー元とコピー先でオブジェクトのidは異なりどちらかを変更するともう片方が変更されるという事は無くなります。実際下記の例ではいずれの方法でコピーしてもコピー先オブジェクトのidはコピー元と異なっています。
以下の方法の中で2つ目のdeepcopyを使う方法だけ深いコピーとなっていて毛色が異なりますが、リストの中にリストがあるといった多次元構造でない限り浅いコピーと表面的には変わりはありません。浅いコピーと深いコピーについては別記事(浅いコピーと深いコピーを速攻で理解する)にまとめていますのでご興味があればご参照下さい。
- copyモジュールのcopy関数を使う(浅いコピー)
- copyモジュールのdeepcopy関数を使う(深いコピー)
- リストのcopyメソッドを使う(浅いコピー)
- リストのスライス[:]を使う(浅いコピー)
- list関数を使う(浅いコピー)
>>> import copy >>> x = ['a', 'b', 'c'] >>> i = copy.copy(x) >>> j = copy.deepcopy(x) >>> k = x.copy() >>> l = x[:] >>> m = list(x) >>> id(x) 4347599240 >>> id(i) 4347598856 >>> id(j) 4347630088 >>> id(k) 4347629896 >>> id(l) 4347629832 >>> id(m) 4347631176
辞書のコピー
「=」による代入の注意点はリストと同じです。辞書のコピー方法は以下の通りですが、これもリストと概ね似ています。
- copyモジュールのcopy関数を使う(浅いコピー)
- copyモジュールのdeepcopy関数を使う(深いコピー)
- 辞書のcopyメソッドを使う(浅いコピー)
- dict関数を使う(浅いコピー)
>>> import copy >>> x = {'a': 0, 'b': 1, 'c': 2} >>> i = copy.copy(x) >>> j = copy.deepcopy(x) >>> k = x.copy() >>> l = dict(x) >>> id(x) 4358107496 >>> id(i) 4356212560 >>> id(j) 4358102760 >>> id(k) 4358107280 >>> id(l) 4358108432
集合のコピー
「=」による代入の注意点はリストと同じです。集合のコピー方法は以下の通りですが、これもリストと概ね似ています。
- copyモジュールのcopy関数を使う(浅いコピー)
- copyモジュールのdeepcopy関数を使う(深いコピー)
- 集合のcopyメソッドを使う(浅いコピー)
- set関数を使う(浅いコピー)
>>> import copy >>> x = {'a', 'b', 'c'} >>> i = copy.copy(x) >>> j = copy.deepcopy(x) >>> k = x.copy() >>> l = set(x) >>> id(x) 4358096936 >>> id(i) 4358096712 >>> id(j) 4358096488 >>> id(k) 4358097384 >>> id(l) 4358097608