web-dev-qa-db-ja.com

Pythonでループの反復を制限するにはどうすればよいですか?

アイテムのリストがあり、最初のいくつかを反復したいとします。

items = list(range(10)) # I mean this to represent any kind of iterable.
limit = 5

素朴な実装

他の言語から来たPythonnaïfは、おそらくこの完全にサービス可能でパフォーマンスの高い(ユニディオマティックの場合)コードを書くでしょう。

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には列挙型があり、そのコードの約半分がうまく含まれています。

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を参照する必要がないため、名前を付ける必要がないことに注意してください。

上記を書く慣用的な方法はありますか?どうやって?

2番目の質問:なぜこれはenumerateに組み込まれないのですか?

36
Aaron Hall

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で同じ動作を得るには、xrangerangeに、Zipitertools.izipに置き換えるだけです。

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))を構成してインデックスを取得する

パブロ・ルイス・ルイスが指摘するように、列挙型でisliceを作成することもできます。

for index, item in enumerate(islice(items, limit)):
    print(index, item)

なぜこれがenumerateに組み込まれないのですか?

純粋なPythonに実装された列挙を以下に示します(コメントで目的の動作を得るために可能な変更を加えています)。

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 = enumerate

def enumerate(collection, start=0, stop=None):
    if stop is not None:
        return Zip(range(start, stop), collection)
    return _enumerate(collection, start)

この追加のチェックは、パフォーマンスへの影響をわずかに無視できます。

whyenumerateには停止引数がないため、これは元々提案されていました( PEP 279 を参照):

この関数は元々、オプションの開始引数と停止引数で提案されました。 GvR [Guido van Rossum]は、関数呼び出しenumerate(seqn, 4, 6)には、シーケンスの4番目と5番目の要素を返すスライスとして別のもっともらしい解釈があると指摘しました。あいまいさを避けるために、ループカウンターとしての柔軟性を失うことを意味する場合でも、オプションの引数は削除されました。その柔軟性は、次のように、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):
    ...

また、制限を理解していない限り、添え字表記によるスライスを避けてください。

68
Aaron Hall

これには itertools.islice を使用できます。 startstop、およびstep引数を受け入れます。1つの引数のみを渡す場合、stopと見なされます。そして、それはどんなイテレータでも動作します。

itertools.islice(iterable, stop)
itertools.islice(iterable, start, stop[, step])

デモ:

>>> from itertools import islice
>>> items = list(range(10))
>>> limit = 5
>>> for item in islice(items, limit):
    print item,
...
0 1 2 3 4

ドキュメントの例:

islice('ABCDEFG', 2) --> A B
islice('ABCDEFG', 2, 4) --> C D
islice('ABCDEFG', 2, None) --> C D E F G
islice('ABCDEFG', 0, None, 2) --> A C E G
21

なぜ単純に使用しないのですか

for item in items[:limit]: # or limit+1, depends
    print(item)    # or whatever function of that item.

これは一部の反復可能オブジェクトに対してのみ機能しますが、リストを指定したため機能します。

セットやディクテーションなどを使用すると機能しません。

19
JeD

次のように、制限またはリストの最後のいずれか早い方までループしないのはなぜですか?

items = range(10)
limit = 5
for i in range(min(limit, len(items))):
  print items[i]

出力:

0
1
2
3
4
1
codeforester

列挙内で制限付きのisliceを渡す

a = [2,3,4,2,1,4]

for a, v in enumerate(islice(a, 3)): 
   print(a, v)

出力:

0 2
1 3
2 4
1
Pablo Ruiz Ruiz