Skip to content

Commit 6aa0fa5

Browse files
committed
bpo-31861: Add operator.aiter and operator.anext
1 parent 096329f commit 6aa0fa5

File tree

3 files changed

+135
-8
lines changed

3 files changed

+135
-8
lines changed

Lib/operator.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010
This is the pure Python implementation of the module.
1111
"""
1212

13-
__all__ = ['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf',
14-
'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand',
15-
'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul',
16-
'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift',
17-
'is_', 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le',
18-
'length_hint', 'lshift', 'lt', 'matmul', 'methodcaller', 'mod',
19-
'mul', 'ne', 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift',
20-
'setitem', 'sub', 'truediv', 'truth', 'xor']
13+
__all__ = [
14+
'abs', 'add', 'aiter', 'anext', 'and_', 'attrgetter', 'concat', 'contains',
15+
'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd',
16+
'iand', 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul',
17+
'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_',
18+
'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint',
19+
'lshift', 'lt', 'matmul', 'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_',
20+
'or_', 'pos', 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor',
21+
]
2122

2223
from builtins import abs as _abs
24+
from collections.abc import AsyncIterable, AsyncIterator
2325

2426

2527
# Comparison Operations *******************************************************#
@@ -408,6 +410,55 @@ def ixor(a, b):
408410
return a
409411

410412

413+
# Asynchronous Iterator Operations ********************************************#
414+
415+
async def aiter(*args):
416+
"""aiter(async_iterable) -> async_iterator
417+
aiter(async_callable, sentinel) -> async_iterator
418+
419+
An async version of the iter() builtin.
420+
"""
421+
lenargs = len(args)
422+
if lenargs != 1 and lenargs != 2:
423+
raise TypeError(f'aiter expected 1 or 2 arguments, got {lenargs}')
424+
if lenargs == 1:
425+
obj, = args
426+
if not isinstance(obj, AsyncIterable):
427+
raise TypeError(f'aiter expected an AsyncIterable, got {type(obj)}')
428+
async for i in obj.__aiter__():
429+
yield i
430+
return
431+
# lenargs == 2
432+
async_callable, sentinel = args
433+
while True:
434+
value = await async_callable()
435+
if value == sentinel:
436+
break
437+
yield value
438+
439+
440+
async def anext(*args):
441+
"""anext(async_iterator[, default])
442+
443+
Return the next item from the async iterator.
444+
If default is given and the iterator is exhausted,
445+
it is returned instead of raising StopAsyncIteration.
446+
"""
447+
lenargs = len(args)
448+
if lenargs != 1 and lenargs != 2:
449+
raise TypeError(f'anext expected 1 or 2 arguments, got {lenargs}')
450+
ait = args[0]
451+
if not isinstance(ait, AsyncIterator):
452+
raise TypeError(f'anext expected an AsyncIterable, got {type(ait)}')
453+
anxt = ait.__anext__
454+
try:
455+
return await anxt()
456+
except StopAsyncIteration:
457+
if lenargs == 1:
458+
raise
459+
return args[1] # default
460+
461+
411462
try:
412463
from _operator import *
413464
except ImportError:

Lib/test/test_asyncgen.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import inspect
2+
import operator
23
import types
34
import unittest
45

@@ -386,6 +387,80 @@ def tearDown(self):
386387
self.loop = None
387388
asyncio.set_event_loop_policy(None)
388389

390+
def test_async_gen_operator_anext(self):
391+
async def gen():
392+
yield 1
393+
yield 2
394+
g = gen()
395+
async def consume():
396+
results = []
397+
results.append(await operator.anext(g))
398+
results.append(await operator.anext(g))
399+
results.append(await operator.anext(g, 'buckle my shoe'))
400+
return results
401+
res = self.loop.run_until_complete(consume())
402+
self.assertEqual(res, [1, 2, 'buckle my shoe'])
403+
with self.assertRaises(StopAsyncIteration):
404+
self.loop.run_until_complete(consume())
405+
406+
def test_async_gen_operator_aiter(self):
407+
async def gen():
408+
yield 1
409+
yield 2
410+
g = gen()
411+
async def consume():
412+
return [i async for i in operator.aiter(g)]
413+
res = self.loop.run_until_complete(consume())
414+
self.assertEqual(res, [1, 2])
415+
416+
def test_async_gen_operator_aiter_class(self):
417+
loop = self.loop
418+
class Gen:
419+
async def __aiter__(self):
420+
yield 1
421+
await asyncio.sleep(0.01, loop=loop)
422+
yield 2
423+
g = Gen()
424+
async def consume():
425+
return [i async for i in operator.aiter(g)]
426+
res = self.loop.run_until_complete(consume())
427+
self.assertEqual(res, [1, 2])
428+
429+
def test_async_gen_operator_aiter_2_arg(self):
430+
async def gen():
431+
yield 1
432+
yield 2
433+
yield None
434+
g = gen()
435+
async def foo():
436+
return await operator.anext(g)
437+
async def consume():
438+
return [i async for i in operator.aiter(foo, None)]
439+
res = self.loop.run_until_complete(consume())
440+
self.assertEqual(res, [1, 2])
441+
442+
def test_operator_anext_bad_args(self):
443+
self._test_bad_args(operator.anext)
444+
445+
def test_operator_aiter_bad_args(self):
446+
self._test_bad_args(operator.aiter)
447+
448+
def _test_bad_args(self, afn):
449+
async def gen():
450+
yield 1
451+
async def call_with_no_args():
452+
await afn()
453+
async def call_with_3_args():
454+
await afn(gen(), 1, 2)
455+
async def call_with_bad_args():
456+
await afn(1, gen())
457+
with self.assertRaises(TypeError):
458+
self.loop.run_until_complete(call_with_no_args())
459+
with self.assertRaises(TypeError):
460+
self.loop.run_until_complete(call_with_3_args())
461+
with self.assertRaises(TypeError):
462+
self.loop.run_until_complete(call_with_bad_args())
463+
389464
async def to_list(self, gen):
390465
res = []
391466
async for i in gen:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add the operator.aiter and operator.anext functions. Patch by Josh Bronson.

0 commit comments

Comments
 (0)