リスト内包表記の評価タイミング

Pythonにはリスト内包表記というのがあり、リストなどの全ての要素に処理を施したい場合、1行で簡潔に書ける。

array = [0, 1, 2]
array2 = [x + 1 for x in array]  # <-これ
print(array2)
# => [1, 2, 3]

# 処理的にはこれと一緒
array3 = []
for x in array:
    array3.append(x + 1)
print(array3)
# => [1, 2, 3]

このarray、どのタイミングで評価されるかというと、通常のfor文と一緒で、ループのたびにarrayを参照して、逐次取り出される。

いや、当たり前っちゃ当たり前なのだが……

たとえばlist.extend()を使うとき。list.extend(iterable)は、iterableでlistを拡張する。

array1 = [0, 1, 2]
array2 = [3, 4, 5]
array1.extend(array2)
# => [0, 1, 2, 3, 4, 5]

iterableに「拡張元のlistのリスト内包表記」を用いる場合、listはループのたびに順次拡張される。結果、リスト内包表記がいつまでも終端に達せず、無限ループに陥る。

array = [0, 1, 2]
array.extend(x + 1 for x in array)

# [0,1,2,1,2,3]になるのではなく、
# arrayはループのたびに順次拡張され、無限ループに陥ってメモリを食いつぶして死ぬ
# [0,1,2,1,2,3,2,3,4,3,4,5,...]

for x in array:
    array.append(x + 1)
# 要はこれと同じ。これも無限ループに陥る。


array.extend([x + 1 for x in array])
# => [0, 1, 2, 1, 2, 3]
# 渡す前にリストにしてしまえば、もうarrayのiterationは完了しているので、大丈夫

listの拡張元に同じlistを使うことはあまり無いものの、忘れた頃についやってしまいがちなミス。しかもメモリが死ぬので何もできなくなってやばい。