イテレータを使用する最も速い方法、そして最も Python らしい方法が何であるかを知りたいです。
たとえば、map
副作用として何かを蓄積する組み込みのイテレータを作成したいとします。 の結果は実際には気にせず、map
副作用だけを気にするので、できるだけオーバーヘッドや定型句を少なくして反復処理を済ませたいのです。次のようになります。
my_set = set()
my_map = map(lambda x, y: my_set.add((x, y)), my_x, my_y)
この例では、反復子を一気に実行して にものを蓄積したいだけなのでmy_set
、my_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_set
をmy_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=0
deque を使用してイテレータを消費するための 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 実装の場合。