Pythonでループの反復回数を制限するには?
質問
アイテムのリストがあり、その最初の数個を反復処理したいとします。
items = list(range(10)) # I mean this to represent any kind of iterable.
limit = 5
ナイーブな実装
他の言語から来たPythonの素人は、おそらくこの完全に実用的でパフォーマンスの高い(非idiomaticであるとしても)コードを書くでしょう。
index = 0
for item in items: # Python's `for` loop is a for-each.
print(item) # or whatever function of that item.
index += 1
if index == limit:
break
より慣用的な実装
しかし、Pythonにはenumerateがあり、このコードの約半分をうまく吸収しています。
for index, item in enumerate(items):
print(item)
if index == limit: # There's gotta be a better way.
break
というわけで、余分なコードを半分に減らすことができました。しかし、もっと良い方法があるはずです。
以下の疑似コードのような動作に近づけることができるでしょうか。
もしenumerateが別のオプションの
stop
引数 (例えば
start
の引数はこのようになります。
enumerate(items, start=1)
のようなもの)が理想的だと思うのですが、下のようなものは存在しません(
のドキュメントを参照してください。
):
# hypothetical code, not implemented:
for _, item in enumerate(items, start=0, stop=limit): # `stop` not implemented
print(item)
の名前をつける必要がないことに注意してください。
index
という名前を付ける必要がないことに注意してください。
上記のような慣用的な書き方はあるのでしょうか?どのように?
二次的な質問ですが、なぜこれはenumerateに組み込まれていないのでしょうか?
どのように解決するのですか?
<ブロッククオートどのように私はPythonでループの反復を制限することができますか?
for index, item in enumerate(items):
print(item)
if index == limit:
break
上記をもっと短く、慣用的に書く方法はないでしょうか?どのように?
インデックスを含む
zip
はその引数の中で最も短い反復記号の上で停止します。(の動作とは対照的です)。
zip_longest
の動作とは対照的です)。
range
は、主な反復記号と一緒に zip に渡すことができる限定的な反復記号を提供することができます。
というわけで
range
オブジェクトを渡すことができます (その
stop
引数) を
zip
に変換し、限定列挙のように使用します。
zip(range(limit), items)
Python 3 を使用します。
zip
と
range
はイテラブルを返し、中間ステップのためにデータをリストに実体化する代わりに、データをパイプライン化します。
for index, item in zip(range(limit), items):
print(index, item)
Python 2で同じ動作をさせるためには、単に
xrange
を
range
と
itertools.izip
に対して
zip
.
from itertools import izip
for index, item in izip(xrange(limit), items):
print(item)
インデックスを必要としない場合。
itertools.islice
を使うことができます。
itertools.islice
:
for item in itertools.islice(items, 0, stop):
print(item)
というように、インデックスに代入する必要がありません。
合成
enumerate(islice(items, stop))
でインデックスを取得します。
Pablo Ruiz Ruiz氏が指摘するように、isliceをenumerateで構成することも可能です。
for index, item in enumerate(islice(items, limit)):
print(index, item)
<ブロッククオート
なぜこれが
enumerate
?
これは純粋なPythonで実装されたenumerateです(コメントで希望の動作を得るために可能な修正を加えています)。
def enumerate(collection, start=0): # could add stop=None
i = start
it = iter(collection)
while 1: # could modify to `while i != stop:`
yield (i, next(it))
i += 1
上記の方法は、すでにenumerateを使っている人にとっては、反復のたびに停止するかどうかをチェックする必要があるため、パフォーマンスが低下します。stop引数を取らない場合は、古いenumerateをチェックし、使用すればよいのです。
_enumerate = enumerate
def enumerate(collection, start=0, stop=None):
if stop is not None:
return zip(range(start, stop), collection)
return _enumerate(collection, start)
この余分なチェックは、パフォーマンスへの影響をわずかに無視できる程度にするでしょう。
に関しては なぜ が stop 引数を持たないかについてですが、これは元々提案されていたものです ( PEP 279 ):
この関数は、当初はオプションの start と stop の引数で提案されました。 GvR [Guido van Rossum] は、この関数呼び出しが
enumerate(seqn, 4, 6)
という別の、もっともらしい解釈があることを指摘しました。 スライスとして解釈することもできます。 という解釈があることを指摘した。 この曖昧さを回避するために、オプションの引数は削除されました。 ループカウンタとしての柔軟性を失うことを意味しますが、optionalな引数は削除されました。 この柔軟性は、次のような一般的なケースで最も重要なものでした。 のように 1 からカウントする一般的なケースで最も重要でした。for linenum, line in enumerate(source,1): print linenum, line
だからどうやら
start
は非常に貴重なものだからということで保管され
stop
はユースケースが少なく、新しい関数の使い方の混乱を招いたため、削除されました。
添え字記法によるスライスを回避
別の回答では
なぜ単純に
for item in items[:limit]: # or limit+1, depends
デメリットを紹介します。
- スライスを受け付けるイテラブルに対してのみ動作するため、より制限されます。
- もしスライスを受け入れるなら、それは通常、参照データ構造を反復する代わりに、メモリ内に新しいデータ構造を作成し、したがって、メモリを浪費します(すべての組み込みオブジェクトはスライスされるとコピーを作成しますが、例えば、numpyの配列はスライスされるとビューを作成します)。
- スライスできないイテラブルは、他の種類の処理を必要とするでしょう。遅延評価モデルに切り替える場合、スライスを伴うコードも変更する必要があります。
制限と、それがコピーを作るのかビューを作るのかを理解したときだけ、添え字記法によるスライシングを使用すべきです。
まとめ
私は、Python コミュニティが enumerate の使い方を知っている今、混乱のコストは引数の価値によって上回ると推測しています。
そのときまで、あなたは使うことができます。
for index, element in zip(range(limit), items):
...
または
for index, item in enumerate(islice(items, limit)):
...
または、インデックスが全く必要ない場合。
for element in islice(items, 0, limit):
...
また、制限を理解していない限り、添え字記法でのスライスは避けましょう。
関連
-
[解決済み] Pythonで現在時刻を取得する方法
-
[解決済み] Pythonで辞書に新しいキーを追加するにはどうすればよいですか?
-
[解決済み] Pythonで2つのリストを連結する方法は?
-
[解決済み] Pythonの辞書からキーを削除するにはどうしたらいいですか?
-
[解決済み】ネストされたディレクトリを安全に作成するには?
-
[解決済み】Pythonに三項条件演算子はありますか?
-
[解決済み】2つの辞書を1つの式でマージする(辞書の和をとる)には?)
-
[解決済み] django.db.migrations.exceptions.InconsistentMigrationHistory
-
[解決済み] virtualenv の `--no-site-packages` オプションを元に戻す。
-
[解決済み] Python Empty Generator 関数
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Pythonの構文に新しいステートメントを追加することはできますか?
-
[解決済み] Flaskで1時間ごとに関数を実行するようにスケジュールするには?
-
[解決済み] django.db.migrations.exceptions.InconsistentMigrationHistory
-
[解決済み] Python 2.7サポート終了?
-
[解決済み] データフレームをソートした後にインデックスを更新する
-
[解決済み] 文字列から先頭と末尾のスペースを削除するには?
-
[解決済み] サブフォルダからのインポートモジュール
-
[解決済み] Django で全てのリクエストヘッダを取得するにはどうすれば良いですか?
-
[解決済み] Python Empty Generator 関数
-
[解決済み] Python 言語を決定するには?