088:多次元配列をループする
2次元のリストの全要素にアクセスするには二重の for 文を用います。次のコードは 3x3 のリストの全要素にアクセスする例です。
>>> L2 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >>> for x in L2: ... for y in x: ... print y ... 1 2 3 4 5 6 7 8 9
次元が増えた場合も、同様に for 文を増やしていくことで取り出すことができます。 3x3x2 のリストの例です。
>>> L3 = [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]] >>> for x in L3: ... for y in x: ... for z in y: ... print z ... 1 2 3 4 5 6 7 8 9 10 11 12
リスト自身に変更も加える場合は、添え字でアクセスしたほうが便利かもしれません。 range 関数は連番を発生させるのに便利な組み込み関数です。
>>> L2 = [[1, 2, 3], [4, 5, 6], [7, 8 ,9]] >>> for i in range(3): ... for j in range(3): ... L2[i][j] = L2[i][j] * i ... >>> L2 [[0, 0, 0], [4, 5, 6], [14, 16, 18]]
リストが何重になっているか事前に分かる場合は上記のコードらで問題ありません。しかし、何重なのか不明の場合、 [1, [2, 3]?, [[4, 5, 6]]?] のような要素とリストが混ざっているリストの場合、含まれるデータの型が分からないといった場合は、話がややこしくなってきます。たとえば、全要素にアクセスするジェネレータとして次のようなものを作ってみるとします。
def traverse_unsafe(iterable):
try:
# イテレート可能かどうか確認
i = iter(iterable)
except TypeError, e:
# イテレート不可、データなのでそのまま返す
value = iterable
yield value
else:
# イテレート可、中身を取り出して返す
for i2 in i:
for value in traverse_unsafe(i2):
yield value
>>> for i in traverse_unsafe([1, [2, 3], [[4, 5, 6]]]): ... print i ... 1 2 3 4 5 6
一見、うまく動いているように見えます。しかし、 Python の基本型、標準モジュールが提供するクラスにはイテレート可能である型が意外に多い、という落とし穴があります。たとえば、文字列はイテレート可能かつ1字の文字列(イテレート可能)を返すので、先ほどの traverse_unsafe ジェネレータにかけると無限ループに陥り、スタックを使い切ってしまいます。
>>> for s in traverse_unsafe(['foo', ['bar', 'baz']]):
... print s
File "<stdin>", line 12, in traverse_unsafe
(中略)
File "<stdin>", line 12, in traverse_unsafe
RuntimeError: maximum recursion depth exceeded
結論、この手の関数・ジェネレータには汎用性を求めすぎないほうがよいと思われます。入力の幅が広い関数・ジェネレータの作成には困難が伴います。この traverse_unsafe ジェネレータはそのまま使うのではなく具体的なコードを作成する際の参考・出発点程度に止めておくべきです。入力をチェックする関数でラップしたりするのもよいでしょう。
少しだけ改良を加えてみます。文字列を含むリストにも対処するために、型チェックを入れると次のようになります。組み込み型の str, unicode 型が入ってきた場合の問題を解決しただけなので、このジェネレータを異常動作させうる入力はまだまだ存在します。
def traverse_unsafe2(iterable):
# 文字列かどうか確認
if isinstance(iterable, basestring):
# 文字列なのでそのまま返す
string = iterable
yield string
else:
try:
# イテレート可能かどうか確認
i = iter(iterable)
except TypeError, e:
# イテレート不可、データなのでそのまま返す
value = iterable
yield value
else:
# イテレート可、中身を取り出して返す
for i2 in i:
for value in traverse_unsafe2(i2):
yield value
>>> list(traverse_unsafe2(['foo', ['bar', 'baz']])) ['foo', 'bar', 'baz']