静かなる名辞

pythonとプログラミングのこと


【python】内包表記をbreakする方法を考える

 リスト内包表記や辞書内包表記、ジェネレータ式などの内包表記は便利ですが、途中で止めたいときがあったとして(あるかどうかは知りませんが)どうしたら良いのでしょう?

カウンタを使う

 こういう方法を真っ先に思いつきます。

>>> [x for x in range(20) if x < 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 enumerateを使うと任意のiterableに対して使えます。

>>> [x for i, x in enumerate("hoge"*100) if i < 10]
['h', 'o', 'g', 'e', 'h', 'o', 'g', 'e', 'h', 'o']

 欠点としては、内包表記のループ対象のiterableの計算コストが高い場合は無意味になることが挙げられると思います。また、コードが冗長な感があります。

StopIteration例外を投げる

 こちらで紹介されている方法です。

Python の内包表記の使い方まとめ - Life with Python

def end_of_loop():
    raise StopIteration

list(x if x < 10 else end_of_loop() for x in range(100))
# 次の書き方でもOK
# list(x if x < 10 else next(iter([])) for x in range(100))

 これ以上良い方法は簡単には思いつきません。だけど、ちょっと冗長でわかりづらい感じがします。

 残念ながらraiseは式ではなく文なので、普通には内包表記の子に入れられません。

 また、これで止めるとリスト内包表記、辞書内包表記ではそのまま例外が戻ってきます。ジェネレータ式だからできる芸当と言えるでしょう。

itertools.takewhile

 素直にitertools.takewhileを使うべきでは? という考え方もあります。

10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.6.5 ドキュメント

>>> t = takewhile(lambda x:x < 10, (x for x in range(20)))
>>> t
<itertools.takewhile object at 0x7f2b68c049c8>
>>> list(t)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 すべてをジェネレータで作る前提でコードを書けば使えると思います。

その他

 黒魔術を使うとやり方は色々あると思いますが、ここでは触れません。

まとめ

 素直にfor文のループで書けばいいと思いました(小学生並みの感想)。