今日、Pythonをいじっているとリスト内包表記でハマったので、その経験をメモしておこうと思う。
問題
x = ['a', 'b', 'c']
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = [y for y in x for x in a]
print(b)
(意味ありげな1行目は置いておいて)この実行結果は、
['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c']
となった。
僕は、
[1, 2, 3, 4, 5, 6, 7, 8, 9]
となることを期待していた。というのも、リスト内包表記ではforの順序は関係ないと思っていたので、リスト平坦化のイディオムである、
x = ['a', 'b', 'c']
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = [y for x in a for y in x]
print(b)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
からの類推でそうなるものだと思っていた。
理由
リスト内包表記は一種の糖皮構文で、それをforで書き下せばわかりやすくなる。
問題の、
b = [y for y in x for x in a]
は、
b = []
for y in x:
for x in a:
b += [y]
と同じことだ。
ここで、for y in x
のx
は、この行が読み込まれた時の状態で固定されることに注意する。例えば、
x = [1, 2, 3]
for y in x:
x = ['a', 'b', 'c']
print(y)
# 1
# 2
# 3
となる。
これをもう少し分かり易く書けば、
for y in [1, 2, 3]:
x = ['a', 'b', 'c']
print(y)
という感じで実行されるので、for文内でx
に別の値を代入しても、for文の実行には影響しない。
以上のことに注意して、
x = ['a', 'b', 'c']
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = [y for y in x for x in a]
print(b)
をfor文で書き直せば、
b = []
for y in ['a', 'b', 'c']:
for x in [[1, 2, 3], [4, 5, 6], [7, 8, 9]]:
b += [y]
print(b)
となる。これの実行結果が、
['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c']
となることに疑問はないだろう。
リストの平坦化
二重リストを平坦化(一重リスト化)する方法として、
x = ['a', 'b', 'c']
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = [y for x in a for y in x]
print(b)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
を紹介したが、これもfor文で書き下して見る。
b = []
for x in [[1, 2, 3], [4, 5, 6], [7, 8, 9]]:
for y in x:
b += [y]
print(b)
となるので、二重リストを平坦化できるのである。