cover_effective_python

【 Effective Python, 2nd Edition 】内包表記に含める for 文や if 文の数は2つ位までに抑えておかないと読解性が極端に悪くなりますよ、という話 投稿一覧へ戻る

Tags: Python , comprehension , Effective

Published 2020年6月25日19:11 by T.Tsuyoshi

内包表記 (comprehension) には複数の for 文を含めることができます。


例えば、for ステートメントを2つ含んだリスト内包表記で matrix をフラットなリストに変換しようと思います。
同じレベルで並んでいる for 文は左から右へと処理されます。


matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]


flat = [x for row in matrix for x in row]


print(flat)

# [1, 2, 3, 4, 5, 6, 7, 8, 9]




リスト内包表記を利用することで簡潔に、読みやすいコードで記述することができていますね。


複数の for 文を含むリスト内包表記が有効なもう1つの場面は、上で利用したような二次元配列をコピーする操作です。
今回は同時に各セルに含まれる値を2乗してみましょう。


squared_matrix = [[i**2 for i in row] for row in matrix]


print(squared_matrix)

# [[1, 4, 9], [16, 25, 36], [49, 64, 81]]




これを for 文を利用した通常の構文で記述すると次のようになるでしょう。


squared_matrix = []
for row in matrix:
each_row = []
for i in row:
each_row.append(i**2)
squared_matrix.append(each_row)


print(squared_matrix)

# [[1, 4, 9], [16, 25, 36], [49, 64, 81]]




リスト内包表記では [] が2重になってしまっていますが、十分に読解性が保たれている上に、コードがかなり簡潔になっています。


では、三次元配列をターゲットにした場合はどうでしょうか?フラットなリストを作成してみます。


three_d_matrix = [
[[1, 2, 3], [ 4, 5, 6]],
[[7, 8, 9], [10, 11, 12]]
]


flat = [i for row in three_d_matrix
for i_row in row
for i in i_row]


print(flat)

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]




読みやすさを考慮して複数行に分割して記述してみました。
通常の for ループ構文でも記述してみましょう。


flat = []
for row in three_d_matrix:
for i_row in row:
flat.extend(i_row)


print(flat)

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]




リスト内包表記を利用してもそれほどコード量は少なくなっていませんし、読解性も ??? ではないでしょうか。
ちなみに、配列構成はそのままコピーし、各要素だけを2乗する処理では次のようになります。


squared_matrix = [[[i**2 for i in sublist2]
for sublist2 in sublist1]
for sublist1 in three_d_matrix]


print(squared_matrix)

# [[[1, 4, 9], [16, 25, 36]], [[49, 64, 81], [100, 121, 144]]]




次数が増加していますから、当然ながら [] の記述量も増えており、ちょっと目にウルサいですね。
それに、このコードをパッと見せられてすぐに解読できる人は少ないと思います。


内包表記中には複数の if 文を含めることもできます。
その場合、同じ for 文のレベルに存在する複数の if 文は暗黙的に and 書式である、とみなされます。


a のリストから 4 より大きい偶数の値だけを取り出してリストを作成しましょう。
b の内包表記も c の内包表記も処理内容は同じです。


a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


b = [x for x in a if x > 4 if x % 2 == 0]
c = [x for x in a if x > 4 and x % 2 == 0]


print(b)

# [6, 8, 10]




print(c)

# [6, 8, 10]




if 文はレベルの異なる for 文にそれぞれ記述することもできます。
二次元配列をコピーします。ただし、セル内の要素の合計が 10 以上のセルだけを対象とし、かつ、そのセルの中で 3 で割り切れる要素だけを残します。


matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]


filtered = [[i for i in row if i % 3 == 0]
for row in matrix if sum(row) >= 10]


print(filtered)

# [[6], [9]]




どうでしょう?かなり入り組んできましたね。
もちろんこのような記述が最良のものとして選択される場面もあるかもしれませんが、
開発チーム内で多くの人と協働している場合、特に読解性の面からできるだけ避けたほうがいいように思われます。


特に辞書内包表記 (dictionary comprehension) ではただでさえ key、value という2つのパラメータを扱わなければいけませんから、
よりコードがごちゃごちゃになってしまうことは想像に難くありません。


一般的なルールとしては、内包表記中に含める for 文、if 文は併せて2つまで、ということにしておけば問題はないと思います。

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)