84

要素自体を気にせずに iterable 内のアイテムの数が必要な場合、それを取得する Pythonic の方法は何でしょうか? 今、私は定義します

def ilen(it):
    return sum(itertools.imap(lambda _: 1, it))    # or just map in Python 3

lambdaしかし、私は有害であると見なされることに近いことを理解しており、lambda _: 1確かにきれいではありません.

(これの使用例は、正規表現に一致するテキスト ファイル内の行数をカウントすることですgrep -c。)

4

7 に答える 7

177

itertools.imap()Python 2 またはPython 3 のへの呼び出しはmap()、同等のジェネレーター式に置き換えることができます。

sum(1 for dummy in it)

これも遅延ジェネレーターを使用するため、メモリ内のすべての反復子要素の完全なリストを具体化することを回避します。

于 2011-03-21T22:37:14.623 に答える
43

より大きな入力に対するスワップのスラッシングと再割り当てのオーバーヘッドを回避するためにsum(1 for i in it)( とは異なり) 固定のメモリ オーバーヘッド動作を維持しながら、反復可能オブジェクトが長い場合よりも有意に高速である (反復可能オブジェクトが短い場合でも有意に遅くない)メソッド:len(list(it))

# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip

from collections import deque
from itertools import count

# Avoid constructing a deque each time, reduces fixed overhead enough
# that this beats the sum solution for all but length 0-1 inputs
consumeall = deque(maxlen=0).extend

def ilen(it):
    # Make a stateful counting iterator
    cnt = count()
    # zip it with the input iterator, then drain until input exhausted at C level
    consumeall(zip(it, cnt)) # cnt must be second zip arg to avoid advancing too far
    # Since count 0 based, the next value is the count
    return next(cnt)

len(list(it))CPython で C コードのループを実行するように ( deque、すべて C で実装されています) count; zip通常、ループごとのバイト コードの実行を回避することが、CPython でのパフォーマンスの鍵となります。

パフォーマンスを比較するための公正なテストケースを考え出すのは驚くほど困難です (任意の入力 iterable では利用できない可能性が高いlistチートを使用し、提供しない関数には、各ループで値が返されたときに高速に動作する特別な動作モードがあることがよくあります)。次の値が要求される前に解放/解放されます。私が使用したテスト ケースは、Python 3.3+ を使用して、入力を受け取り、特別なリターン コンテナーの最適化またはを欠いた C レベルのジェネレーターを返すジェネレーター関数を作成することでした。__length_hint__itertools__length_hint__dequemaxlen=0itertools__length_hint__yield from

def no_opt_iter(it):
    yield from it

次に、ipython %timeit魔法を使用します(100をさまざまな定数に置き換えます):

>>> %%timeit fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))

入力がメモリの問題を引き起こすほど大きくない場合、 Python 3.9 x64 を実行している Linux ボックスでは、入力の長さに関係なくlen(list(it))、私のソリューションは よりも約 50% 長くかかります。def ilen(it): return len(list(it))

consumeall最小の入力の場合、ロード/呼び出しのセットアップ コストは、zip///よりも無限に時間がかかることを意味しますcount(長さ 0 の入力の場合、私のマシンでは約 40 ns 長く、単純なアプローチよりも 10% 増加します)。長さ 2 の入力にヒットした場合、コストは同等であり、長さ 30 前後のどこかで、実際の作業と比較して初期オーバーヘッドは目立ちません。このアプローチは、約 50% 長くかかります。nextdef ilen(it): sum(1 for _ in it)sumsum

基本的に、メモリの使用が重要な場合、または入力のサイズに制限がなく、簡潔さよりも速度を重視する場合は、このソリューションを使用してください。入力が制限されていて小さい場合len(list(it))はおそらく最適であり、制限がないが単純さ/簡潔さが重要な場合は、 を使用しますsum(1 for _ in it)

于 2015-12-21T21:22:43.047 に答える
9

簡単な方法は次のとおりです。

def ilen(it):
    return len(list(it))

多数の要素 (たとえば、数万以上) を生成している場合、それらをリストに入れるとパフォーマンスの問題になる可能性があることに注意してください。ただし、これは、ほとんどの場合、パフォーマンスは問題にならないという考えを単純に表現したものです。

于 2011-03-21T22:39:10.910 に答える
2

これにはカーディナリティパッケージが気に入っています。非常に軽量で、イテラブルに応じて利用可能な最速の実装を使用しようとします。

使用法:

>>> import cardinality
>>> cardinality.count([1, 2, 3])
3
>>> cardinality.count(i for i in range(500))
500
>>> def gen():
...     yield 'hello'
...     yield 'world'
>>> cardinality.count(gen())
2
于 2016-04-15T10:28:04.150 に答える