イテレータを使用する最も高速な(最もPython的な)方法 質問する

イテレータを使用する最も高速な(最もPython的な)方法 質問する

イテレータを使用する最も速い方法、そして最も Python らしい方法が何であるかを知りたいです。

たとえば、map副作用として何かを蓄積する組み込みのイテレータを作成したいとします。 の結果は実際には気にせず、map副作用だけを気にするので、できるだけオーバーヘッドや定型句を少なくして反復処理を済ませたいのです。次のようになります。

my_set = set()
my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y)

この例では、反復子を一気に実行して にものを蓄積したいだけなのでmy_setmy_setを実際に実行するまでは は単なる空のセットですmy_map。次のようになります。

for _ in my_map:
    pass

または裸の

[_ for _ in my_map]

動作しますが、どちらも扱いにくい感じがします。副作用の恩恵を受けられるように、イテレータが迅速に反復処理を実行するための、より Python らしい方法はあるでしょうか?


基準

上記の 2 つの方法を次の内容でテストしました。

my_x = np.random.randint(100, size=int(1e6))
my_y = np.random.randint(100, size=int(1e6))

およびmy_setmy_map上記で定義したとおりに使用します。timeit を使用すると、次の結果が得られました。

for _ in my_map:
    pass
468 ms ± 20.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

[_ for _ in my_map]
476 ms ± 12.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

両者の間に実質的な違いはなく、どちらも不格好に感じます。

注:list(my_map)コメントで提案された でも同様のパフォーマンスが得られました。

ベストアンサー1

副作用のためだけにマップオブジェクトを作成するべきではありませんが、実際にはイテレータを使用する標準的なレシピがあります。itertoolsドキュメント:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is None, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

「完全に消費する」ケースだけの場合、これは次のように簡略化できます。

def consume(iterator):
    collections.deque(iterator, maxlen=0)

この方法を使用するとcollections.deque、すべての要素を保存する必要がなくなり(maxlen=0)、バイトコード解釈のオーバーヘッドなしでCの速度で反復処理が行われます。専用高速パスmaxlen=0deque を使用してイテレータを消費するための deque 実装。

タイミング:

In [1]: import collections

In [2]: x = range(1000)

In [3]: %%timeit
   ...: i = iter(x)
   ...: for _ in i:
   ...:     pass
   ...: 
16.5 µs ± 829 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %%timeit
   ...: i = iter(x)
   ...: collections.deque(i, maxlen=0)
   ...: 
12 µs ± 566 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

もちろん、これはすべてCPythonに基づいています。インタープリタのオーバーヘッドの性質は他のPython実装とはまったく異なり、maxlen=0高速パスはCPythonに特有のものです。abarnertの回答他の Python 実装の場合。

おすすめ記事