1. ホーム
  2. python

Pythonでループの反復回数を制限するには?

2023-11-09 05:11:17

質問

アイテムのリストがあり、その最初の数個を反復処理したいとします。

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 を使用します。 ziprange はイテラブルを返し、中間ステップのためにデータをリストに実体化する代わりに、データをパイプライン化します。

for index, item in zip(range(limit), items):
    print(index, item)

Python 2で同じ動作をさせるためには、単に xrangerangeitertools.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):
    ...

また、制限を理解していない限り、添え字記法でのスライスは避けましょう。