2009年10月21日水曜日

共有リファレンス

Pythonでは、すべての値はオブジェクトである。変数はオブジェクトに対するリファレンス(C言語のポインタ、Javaの参照のようなもの)を持つ。

>>> a = 1
上記の代入が実行されると、1というオブジェクトが生成され(※)変数aはそのオブジェクトに対するリファレンスを持つ。
>>> b = a
変数bに変数aのリファレンスを代入する。(オブジェクト1を代入するという表現は不適切)

>>> a = a + 1 --- (1)
上記の代入を行うと、aは2になる。
>>> print a
2
だが、bの値は変わらない。
>>> print b
1

(1)では、まずa+1が計算される。その結果として2というオブジェクトが生成され、そのオブジェクトに対するリファレンスが変数bに代入される。(1)が実行されるまでは、aとbは同じオブジェクトに対するリファレンスを持っているが、(1)が実行された瞬間にリファレンスの共有状態が解除されるので注意する必要がある。
この動作は、CやC++になれたプログラマであれば違和感を感じるかもしれないが、一般的には理解しやすいと思う。

実は先ほど取り上げた整数オブジェクトは不変オブジェクトであり、生成された後で値を変更することができないため、共有リファレンスに起因する問題が生じることはない。しかし、すべてのオブジェクトが上記のようにわかりやすい動作をするわけではない。

共有リファレンスが問題になる例
>>> x = [1,2,3]
>>> y = x
>>> x
[1, 2, 3]
>>> y
[1, 2, 3]
>>> x.append(4)
>>> x
[1, 2, 3, 4]
>>> y
[1, 2, 3, 4]

この例では、xに対して行った操作( x.append(4) )がyに影響を与えている。これは、リストが可変型のオブジェクトだからだ。リストの内容は大きくなる場合が多く、不変オブジェクトとして実装した場合、動作速度に問題がでるためこのようになっているそうだが、少し注意して使えば便利な仕様である。Javaでも、Stringクラスは不変、ArrayListなどは可変のオブジェクトとなっているので、あまり違和感はないと思う。

もちろん、この仕様を回避する方法はある。

個別に定義する
>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3]

スライシングを使ってオブジェクトを複製
>>> a = [1,2,3]
>>> b = a[:]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3]

※整数値1は、すでに実行環境に存在するはずなので、実際には新しいオブジェクトは生成されない。実行速度を向上させるために、同じ値のオブジェクトがあれば再利用するようになっている。

0 件のコメント:

コメントを投稿