読者です 読者をやめる 読者になる 読者になる

配列の初期化での注意

Python

Pythonで1次元の配列を生成する場合、

x = [0 for i in xrange(5)]
print x

とすると

[0, 0, 0, 0, 0]

という出力が得られます。


しかしこれを

x = [0] * 5
print x

とすると、実はかなり高速になります。


以下のようなコードで速度を計測してみると、

import timeit

src1 = """
x = [0 for i in xrange(5)]
"""

src2 = """
x = [0] * 5
"""

t = timeit.Timer(src1)
print 'src1 is %s [sec]' % t.timeit(100000) 

t = timeit.Timer(src2)
print 'src2 is %s [sec]' % t.timeit(100000) 

出力結果は以下のようになります。

src1 is 0.213823823978 [sec]
src2 is 0.114878566969 [sec]

その差、2倍近くあります。


そのため、私は普段上記のsrc2のような方法で
初期化済みの配列を作るようにしているのですが、
このとき注意しなければならないことがあります。


それは、[x] * y という書き方だと x がオブジェクトの時に
値が複製されるのではなく参照が複製されてしまうということです。


例えば

class A(object):
    def __init__(self, v):
        self.value = v
    def set(self, v):
        self.value = v
    def __str__(self):
        return 'A(%d)' % self.value

arr = [A(1)] * 5
arr[1].set(99)
print '/'.join(map(str, arr))

というコードを実行すると、

A(1)/A(99)/A(1)/A(1)/A(1)

という出力を期待してしまいそうですが、
実際には

A(99)/A(99)/A(99)/A(99)/A(99)

という出力が得られてしまいます。


なんだそんなことか、と思われてしまいそうですが、
Pythonのコードで各要素をゼロで初期化した2次元配列を作る場合などに
うっかり想定外の動作をしてしまうことがあるので注意が必要です。

arr = [[0] * 5] * 3
print arr

とすると

[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

という5×3の2次元配列が生成できますが、
このような方法で2次元配列を作ると

arr[1][2] = 99
print arr

としたときに

[[0, 0, 0, 0, 0], [0, 0, 99, 0, 0], [0, 0, 0, 0, 0]]

ではなく

[[0, 0, 99, 0, 0], [0, 0, 99, 0, 0], [0, 0, 99, 0, 0]]

という出力になってしまうのです。


多次元配列を使うことなんて画像処理とか
パズルを解くときくらいしかありませんが、
気を付ける必要がありそうです。


ちなみに私はパズルを解いていてうっかり上記のミスをやらかしてしまいました。
まだまだ修行が足りませんね。