リストにミュータブルインスタンス

ちょこっと注意しよう!

tags:python, tips, experiment, issue, list, mutable
created:2007-02-05T20:24:24

ちょこっと注意しよう! リストにミュータブルインスタンスを入れるときには注意が必要

配列として作成するリスト

ar = [0] * 16

このようにすると、ゼロで初期化された16要素のリストを作成できます。

各要素の参照、変更などでも問題は発生しません。

で、次に2次元配列のようなものを作成したくて

ar2 = [[0] * 16] * 8

なんてことをしてしまうと問題が発生します。

これは以下のコードにほど等しいことを行っていることに注意してください。

d = [0] * 16
ar2 = [d,d,d,d,d,d,d,d]

つまり、「16要素リストのインスタンス」を8回参照したリストを作成したことになります。

>>> import sys
>>> d = [0] * 16
>>> ar2 = [d,d,d,d,d,d,d,d]
>>> sys.getrefcount(d)
10

「sys.getrefcount」は引数としての参照数も含んで報告してくれますので、 10参照カウントという結果になります。

このように参照カウントが増えるだけで 「16要素リストのインスタンス」のコピーが8要素配列にセットされるわけではないため、 当然、以下のような操作をするとおかしなことに。

>>> ar2[0][0]=123
>>> ar2
[[123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
, 0, 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0]]

一箇所書き換えただけなのに、8要素すべてに反映されていますね。

このような問題は8要素に入れるオブジェクトがイミュータブルであれば問題は発生しません。 また、以下のように要素をそれぞれタイプした場合、 各インスタンスが作成されるので問題が出ません。

>>> ar = [[0],[0],[0]]
>>> ar[0][0]
0
>>> ar[0][0]=123
>>> ar
[[123], [0], [0]]

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

  • bool
  • int
  • long
  • float
  • complex
  • str
  • unicode
  • tuple
  • クラス
  • メソッド
  • 関数
  • タイプ

などがあります。

  • set
  • list
  • dict
  • 自作

といったオブジェクトを内包するときは注意が必要です!

感想

CのアルゴリズムをPythonに移植している時に躓いた時の原因報告です。

リストと「Cでいうところの配列」は組み立て手順が違うということを認識しよう!

多次元配列を作る時は各リストを生成してから親リストに追加という手順が Pythonicといえるかもしれません。

リストがミュータブルでかつコピーオンライト処理を持っていないことで 今回のような問題に出くわしたわけです。

イミュータブルだと定義づけた場合、 コピーオンライト処理が不要になるのは当然なのですが、 ある意味いい割り切り方だと思いました。 それでPythonの文字列がイミュータブルなのも納得できました。