この章では、オブジェクトの取り扱いにおいて、陥りやすいプログラムミスについて解説します。
オブジェクトの代入
まずは以下のプログラム例を見てください。
5行目でlist2の内容をlist1へ代入後、8行目でlist1の先頭要素を100に変更しています。
その結果、list1は[100,50,60]に変更、list2は[40,50,60]で変わらないはずです。
しかし、実行結果はその様になりません。
list1は[100,50,60]ですが、list2は[40,50,60]ではなく、[100,50,60]に変わってしまいます。
なぜ、このような現象が起きるのでしょうか。
<プログラム例>
list1 = [10,20,30] list2 = [40,50,60] # list2をlist1へ代入する list1 = list2 # list1の先頭要素[0]を100に変更する list1[0] = 100 # list1とlist2の内容を表示する print('list1={}'.format(list1)) print('list2={}'.format(list2))
<実行結果>
list1=[100, 50, 60]
list2=[100, 50, 60]
実はPythonでは、1-2行目の「変数へのオブジェクト代入」と5行目の「変数から変数への代入」が次のような仕組みになっているからです。
1-2行目の「変数へのオブジェクト代入」
オブジェクト[10,20,30]と[40,50,60]は、変数lits1とlist2へ直接格納されるのではなく、実はオブジェクト[10,20,30]と[40,50,60]にそれぞれidentity値という固有番号が割り振られ、変数list1とlist2へはそのidentity値が格納される。
イメージ
・オブジェクト[10,20,30]・・・identity番号01
・オブジェクト[40,50,60]・・・identity番号02
・変数list1 = identity番号01
・変数list2 = identity番号02
5行目の「変数から変数への代入」
これはlist2の内容をlist1へ代入(コピー)しているのではなく、実はlist2のidentity値をlist1へ代入しているのです。
この代入操作で、list1のidentity値はlist2のidentity値と同じ値になったことになります。
イメージ
・オブジェクト[10,20,30]・・・identity番号01
・オブジェクト[40,50,60]・・・identity番号02
・変数list1 = identity番号02 ←オブジェクトではなくidentity値が代入された
・変数list2 = identity番号02
そして、8行目の「list1[0] = 100」で、
list1のidentity値(=list2のidentity値)に対応するオブジェクト [40,50,60]の先頭要素が100に変更されているのです。
イメージ
・オブジェクト[10,20,30]・・・identity番号01
・オブジェクト[100,50,60]・・・identity番号02
・変数list1 = identity番号02
・変数list2 = identity番号02
最後に、list1とlist2を表示すると、
どちらもindenty番号02のオブジェクト[100,50,60]を表示する結果になるということです。
この様に、identity値を使って変数とオブジェクトを結びつける方法を参照といいます。
では、indenty値がどの様になっているのかを以下のプログラムで実際に見てみましょう。
<プログラム例>
list1 = [10,20,30] list2 = [40,50,60] # list1とlist2のidentity値をid関数で表示する print('list1のidentity={}'.format(id(list1))) print('list2のidentity={}'.format(id(list2))) # list2をlist1へ代入する list1 = list2 # list1の先頭要素[0]を100に変更する list1[0] = 100 # list1とlist2の内容を表示する print('list1={}'.format(list1)) print('list2={}'.format(list2)) # もう一度、list1とlist2のidentity値をid関数で表示する print('list1のidentity={}'.format(id(list1))) print('list2のidentity={}'.format(id(list2)))
<実行結果>
list1のidentity=2309319917440
list2のidentity=2309324668224
list1=[100, 50, 60]
list2=[100, 50, 60]
list1のidentity=2309324668224
list2のidentity=2309324668224
※identity値はプログラム実行ごとに毎回値が変わります。
では、参照の仕組みがあるためにオブジェクトを意図したとおりにコピーできない問題(参照の問題)をどの様に回避すれば良いでしょうか。
参照の問題を回避するには
list2の内容をlist1へコピーするには、以下のプログラム例ように、list2の要素をlist1の要素へ1つずつ代入する方法があります。
これによって参照の問題を回避することができます。
<プログラム例>
list1 = [10,20,30] list2 = [40,50,60] # list2の要素をlist1の要素へ代入する for i in range(len(list2)): list1[i] = list2[i] # list1の先頭要素[0]を100に変更する list1[0] = 100 # list1とlist2の内容を表示する print('list1={}'.format(list1)) print('list2={}'.format(list2))
<実行結果>
list1=[100, 50, 60]
list2=[40, 50, 60]
※5-6行目で、for文を使ってlist2の要素をlist1の要素へ1つずつ代入することで、参照の問題を回避している。
参照の問題が発生しない例外ケース
全ての場合において、参照の問題が発生するかというと実はそうではなく、参照の問題が発生しない例外があります。
・int型、str型、bool型などの一部の標準的な型
・tuple型(コレクションのタプル)
<プログラム例>
list1 = 10 list2 = 40 # list1とlist2のidentity値をid関数で表示する print('list1のidentity={}'.format(id(list1))) print('list2のidentity={}'.format(id(list2))) # list2をlist1へ代入する list1 = list2 # list1の先頭要素[0]を100に変更する list1 = 100 # list1とlist2の内容を表示する print('list1={}'.format(list1)) print('list2={}'.format(list2)) # もう一度、list1とlist2のidentity値をid関数で表示する print('list1のidentity={}'.format(id(list1))) print('list2のidentity={}'.format(id(list2)))
<実行結果>
list1のidentity=2489747401296
list2のidentity=2489747402256
list1=100
list2=40
list1のidentity=2489747592656
list2のidentity=2489747402256
※list1、list2のオブジェクトはint型のため、参照の問題は発生せず、list2はもとの40のままです。
以上、Pythonでは参照の問題に注意を払いプログラムを行うようにしましょう。