ミュータブルオブジェクトの浅いコピーと深いコピー

目的

リストのようなミュータブルオブジェクトは要素の値を更新するときに注意すべきことがある.ここではイミュータブルオブジェクトの浅いコピーと深いコピーについて学ぶ.

説明

ミュータブルオブジェクト

同じオブジェクトに別名をつける代入文

テストの点数からなるリストの要素の値を更新する以下のようなプログラムを考えよう.

2行目で英語・数学・国語のテストの点数からなるリストoriginal_scoresを作り,3行目で代入文を使用して,assigned_scoresにoriginal_scoresを代入している.4行目でoriginal_scoresの英語の点数を変更し,5,6行目でoriginal_scoresとassigned_scoresを表示している.

このプログラムを実行すると,以下のようになり,assigned_scoresの英語の点数も70点から75点に変更されていることがわかる.

Pythonでは,id関数を使用すると,オブジェクトのid番号を取得できる.各オブジェクトには固有のid番号が振られることになっており,id番号を確認すれば同じオブジェクトか異なるオブジェクトかを確認することができる.また,is演算子を2つのオブジェクトに適用すると,同じオブジェクトの場合はTrue,異なるオブジェクトの場合はFalseが返される.

では,先程の例でid番号を表示し, is演算子で同じオブジェクトかどうかを調べているプログラムを見てみよう.

2行目でoriginal_scoresを作り,3行目でoriginal_scoresのid番号を表示している.4行目でassigned_scoresにoriginal_scoresを代入し,5,6行目でoriginal_scoresとassigned_scoresのid番号を表示している.7行目でoriginal_scoresとassigned_scoresが同じオブジェクトかどうかを表示している.

このプログラムを実行すると,以下のように表示され,original_scoresとassigned_scoresには同じid番号が振られていることがわかる.これは,original_scoresとassigned_scoresが指している実体は同じものであり,同じ実体に2つの名前がつけられた状態だと理解できる.

4行目のように代入文を使用すると,同じオブジェクトに別名がついている状態になっていることを確認しておこう.

新しくオブジェクトを生成し,名前をつける代入文

では,先程と同じように,代入文を使用してリストの値を更新する以下のようなプログラムを考えよう.

3行目までは先程と同様であり,4行目でoriginal_scoresの英語の点数を75点に更新している.

このプログラムを実行すると,以下のようになり,先程の例とは違い,original_scoresの英語の点数だけが70点から75点に変更されていることがわかる.

この例でもid番号を表示し,同じオブジェクトかどうかを確認してみよう.

このプログラムを実行すると,以下のようになり,8行目の後にoriginal_scoresのid番号が変わっていることがわかる.これは,8行目で全く新しいオブジェクトが生成され,そのオブジェクトにoriginal_scoresという名前がつけられているためである.

8行目のように代入文を使用すると,新しくオブジェクトを生成し,そのオブジェクトに名前をつけることになっていることを確認ておこう.

ミュータブルオブジェクトに共通の性質

リストには以上のような性質があることを確認した.このような性質は,ミュータブル(値が変更可能な)オブジェクトに共通の性質である.これまでに学んだミュータブルオブジェクトには,リストの他に辞書と集合がある.

イミュータブルオブジェクト

これまでに学んだイミュータブル(値が変更できない)オブジェクトには,bool型のオブジェクト・整数・浮動小数点数・文字列・タプルがある.ここでは整数の値を更新する例を確認しよう.

2行目で英語のテストの点数70点をenglish_scoreに代入している.4行目でenglish_scoreの値を75点に変更している.3行目と5行目でその時点でのenglish_scoreのid番号を表示させている.

このプログラムを実行すると,以下のように,4行目の後でenglish_scoreのid番号が変更されていることがわかる.

この例でも,4行目ではenglish_scoreの実体の値が書き換わったのではなく,新しく75という整数値のオブジェクトが生成され,それにenglish_scoreという名前がつけられていることがわかる.

同様の例をもう1つ確認しておこう.以下のようにしても英語のテストの点数は変更できる.

4行目で,english_scoreの値に5を加算し,その結果をenglish_scoreに代入している.

このプログラムを実行すると,以下のように,やはり4行目の後でenglish_scoreのid番号が変更されている.

この例でも,4行目ではenglish_scoreの実体の値が更新されたのではなく,新しく計算結果の整数値のオブジェクトが生成され,それにenglish_scoreという名前がつけられていることがわかる.

以上のように,イミュータブルオブジェクトでは値の変更はできず,代入文を使用して値を変更する例では,新しいオブジェクトが生成され,そのオブジェクトに名前がつけられていることを確認しておこう.

ミュータブルオブジェクトのコピー

copyメソッドによるコピー

ミュータブルオブジェクトでは,同じオブジェクトに別名をつける代入文があった.では,別のオブジェクトとしてコピーしたい場合にはどうすればよいかを確認しよう.

3行目で,リストオブジェクトのcopyメソッドを使用してcopied_scoresにリストオブジェクトをコピーしている.

このプログラムを実行すると,以下のように,original_scoresの英語の点数だけが更新されていることが確認できる.

id関数でオブジェクトのid番号を確認しよう.

このプログラムを実行すると,以下のようになり,copied_scoresにはoriginal_scoresとは異なるid番号が振られており,リストオブジェクトがコピーされていることがわかる.

copyメソッドは浅いコピー

では,以下のように,リストの中にリストがある場合の動作を確認しよう.

2,3行目のように,リストの中に3教科のテストの点数のリストがある場合で,copyメソッドによりリストオブジェクトをコピーしている.その後,4行目でoriginal_scoresの学籍番号を,5行目で英語の点数を変更している.

このプログラムを実行すると,以下のようになり,copied_scoresの学籍番号は変更されていないが,英語の点数は変更されていることがわかる.リストオブジェクトのcopyメソッドによるコピーでは,リストの第1階層はコピーされるが,リスト中のリストのようなオブジェクトはコピーされないため,このようなことが起こる.このように第1階層だけがコピーされるようなものを浅いコピーと呼ぶ.

copyモジュールによる深いコピー

リストオブジェクトに対して深いコピーをする場合には,copyモジュールのdeepcopy関数を使用する.

1行目でcopyモジュールをインポートし,6行目でdeepcopy関数を呼び出し,深いコピーをしている.

このプログラムを実行すると,以下のようになり,original_scoreの学籍番号と英語の点数は変更されているが,deepcopied_scoreは変更されておらず,深いコピーがされていることが確認できる.

copyモジュールによる浅いコピー

リストオブジェクトのcopyメソッドの代わりに,copyモジュールのcopy関数を使用しても浅いコピーはできる.

課題

課題0

整数値を代入した変数の値を変更すると,新しく整数オブジェクトが生成されることを確認することで,整数オブジェクトがイミュータブルであるこを確認せよ.

課題1

浮動小数点数を代入した変数の値を変更すると,新しく浮動小数点数オブジェクトが生成されることを確認することで,浮動小数点数オブジェクトがイミュータブルであるこを確認せよ.

課題2

bool型の値を代入した変数の値を変更すると,新しくbool型オブジェクトが生成されることを確認することで,bool型オブジェクトがイミュータブルであるこを確認せよ.

課題3

文字列を代入した変数の値を変更すると,新しく文字列オブジェクトが生成されることを確認することで,文字列オブジェクトがイミュータブルであるこを確認せよ.

課題4

タプルを代入した変数の値を変更すると,新しくタプルオブジェクトが生成されることを確認することで,タプルオブジェクトがイミュータブルであるこを確認せよ.

課題5

リストのリストオブジェクトを作成し,オブジェクトに別名をつけ,浅いコピーをし,深いコピーをし,元のリストオブジェクトの要素の値を変更せよ.

課題6

辞書の辞書オブジェクトを作成し,オブジェクトに別名をつけ,浅いコピーをし,深いコピーをし,元の辞書オブジェクトの要素の値を変更せよ.