Issue #24018: Add a collections.Generator abstract base class.
This commit is contained in:
parent
dae2ef1cfa
commit
bd60e8dece
|
@ -40,6 +40,7 @@ ABC Inherits from Abstract Methods Mixin
|
|||
:class:`Hashable` ``__hash__``
|
||||
:class:`Iterable` ``__iter__``
|
||||
:class:`Iterator` :class:`Iterable` ``__next__`` ``__iter__``
|
||||
:class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
|
||||
:class:`Sized` ``__len__``
|
||||
:class:`Callable` ``__call__``
|
||||
|
||||
|
@ -102,6 +103,15 @@ ABC Inherits from Abstract Methods Mixin
|
|||
:meth:`~iterator.__next__` methods. See also the definition of
|
||||
:term:`iterator`.
|
||||
|
||||
.. class:: Generator
|
||||
|
||||
ABC for generator classes that implement the protocol defined in
|
||||
:pep:`342` that extends iterators with the :meth:`~generator.send`,
|
||||
:meth:`~generator.throw` and :meth:`~generator.close` methods.
|
||||
See also the definition of :term:`generator`.
|
||||
|
||||
.. versionadded:: 3.5
|
||||
|
||||
.. class:: Sequence
|
||||
MutableSequence
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Unit tests are in test_collections.
|
|||
from abc import ABCMeta, abstractmethod
|
||||
import sys
|
||||
|
||||
__all__ = ["Hashable", "Iterable", "Iterator",
|
||||
__all__ = ["Hashable", "Iterable", "Iterator", "Generator",
|
||||
"Sized", "Container", "Callable",
|
||||
"Set", "MutableSet",
|
||||
"Mapping", "MutableMapping",
|
||||
|
@ -50,6 +50,7 @@ dict_values = type({}.values())
|
|||
dict_items = type({}.items())
|
||||
## misc ##
|
||||
mappingproxy = type(type.__dict__)
|
||||
generator = type((lambda: (yield))())
|
||||
|
||||
|
||||
### ONE-TRICK PONIES ###
|
||||
|
@ -124,6 +125,64 @@ Iterator.register(str_iterator)
|
|||
Iterator.register(tuple_iterator)
|
||||
Iterator.register(zip_iterator)
|
||||
|
||||
|
||||
class Generator(Iterator):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next item from the generator.
|
||||
When exhausted, raise StopIteration.
|
||||
"""
|
||||
return self.send(None)
|
||||
|
||||
@abstractmethod
|
||||
def send(self, value):
|
||||
"""Send a value into the generator.
|
||||
Return next yielded value or raise StopIteration.
|
||||
"""
|
||||
raise StopIteration
|
||||
|
||||
@abstractmethod
|
||||
def throw(self, typ, val=None, tb=None):
|
||||
"""Raise an exception in the generator.
|
||||
Return next yielded value or raise StopIteration.
|
||||
"""
|
||||
if val is None:
|
||||
if tb is None:
|
||||
raise typ
|
||||
val = typ()
|
||||
if tb is not None:
|
||||
val = val.with_traceback(tb)
|
||||
raise val
|
||||
|
||||
def close(self):
|
||||
"""Raise GeneratorExit inside generator.
|
||||
"""
|
||||
try:
|
||||
self.throw(GeneratorExit)
|
||||
except (GeneratorExit, StopIteration):
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError("generator ignored GeneratorExit")
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
if cls is Generator:
|
||||
mro = C.__mro__
|
||||
for method in ('__iter__', '__next__', 'send', 'throw', 'close'):
|
||||
for base in mro:
|
||||
if method in base.__dict__:
|
||||
break
|
||||
else:
|
||||
return NotImplemented
|
||||
return True
|
||||
return NotImplemented
|
||||
|
||||
|
||||
Generator.register(generator)
|
||||
|
||||
|
||||
class Sized(metaclass=ABCMeta):
|
||||
|
||||
__slots__ = ()
|
||||
|
|
|
@ -14,7 +14,7 @@ import sys
|
|||
from collections import UserDict
|
||||
from collections import ChainMap
|
||||
from collections import deque
|
||||
from collections.abc import Hashable, Iterable, Iterator
|
||||
from collections.abc import Hashable, Iterable, Iterator, Generator
|
||||
from collections.abc import Sized, Container, Callable
|
||||
from collections.abc import Set, MutableSet
|
||||
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView
|
||||
|
@ -522,6 +522,77 @@ class TestOneTrickPonyABCs(ABCTestCase):
|
|||
return
|
||||
self.assertNotIsInstance(NextOnly(), Iterator)
|
||||
|
||||
def test_Generator(self):
|
||||
class NonGen1:
|
||||
def __iter__(self): return self
|
||||
def __next__(self): return None
|
||||
def close(self): pass
|
||||
def throw(self, typ, val=None, tb=None): pass
|
||||
|
||||
class NonGen2:
|
||||
def __iter__(self): return self
|
||||
def __next__(self): return None
|
||||
def close(self): pass
|
||||
def send(self, value): return value
|
||||
|
||||
class NonGen3:
|
||||
def close(self): pass
|
||||
def send(self, value): return value
|
||||
def throw(self, typ, val=None, tb=None): pass
|
||||
|
||||
non_samples = [
|
||||
None, 42, 3.14, 1j, b"", "", (), [], {}, set(),
|
||||
iter(()), iter([]), NonGen1(), NonGen2(), NonGen3()]
|
||||
for x in non_samples:
|
||||
self.assertNotIsInstance(x, Generator)
|
||||
self.assertFalse(issubclass(type(x), Generator), repr(type(x)))
|
||||
|
||||
class Gen:
|
||||
def __iter__(self): return self
|
||||
def __next__(self): return None
|
||||
def close(self): pass
|
||||
def send(self, value): return value
|
||||
def throw(self, typ, val=None, tb=None): pass
|
||||
|
||||
class MinimalGen(Generator):
|
||||
def send(self, value):
|
||||
return value
|
||||
def throw(self, typ, val=None, tb=None):
|
||||
super().throw(typ, val, tb)
|
||||
|
||||
def gen():
|
||||
yield 1
|
||||
|
||||
samples = [gen(), (lambda: (yield))(), Gen(), MinimalGen()]
|
||||
for x in samples:
|
||||
self.assertIsInstance(x, Iterator)
|
||||
self.assertIsInstance(x, Generator)
|
||||
self.assertTrue(issubclass(type(x), Generator), repr(type(x)))
|
||||
self.validate_abstract_methods(Generator, 'send', 'throw')
|
||||
|
||||
# mixin tests
|
||||
mgen = MinimalGen()
|
||||
self.assertIs(mgen, iter(mgen))
|
||||
self.assertIs(mgen.send(None), next(mgen))
|
||||
self.assertEqual(2, mgen.send(2))
|
||||
self.assertIsNone(mgen.close())
|
||||
self.assertRaises(ValueError, mgen.throw, ValueError)
|
||||
self.assertRaisesRegex(ValueError, "^huhu$",
|
||||
mgen.throw, ValueError, ValueError("huhu"))
|
||||
self.assertRaises(StopIteration, mgen.throw, StopIteration())
|
||||
|
||||
class FailOnClose(Generator):
|
||||
def send(self, value): return value
|
||||
def throw(self, *args): raise ValueError
|
||||
|
||||
self.assertRaises(ValueError, FailOnClose().close)
|
||||
|
||||
class IgnoreGeneratorExit(Generator):
|
||||
def send(self, value): return value
|
||||
def throw(self, *args): pass
|
||||
|
||||
self.assertRaises(RuntimeError, IgnoreGeneratorExit().close)
|
||||
|
||||
def test_Sized(self):
|
||||
non_samples = [None, 42, 3.14, 1j,
|
||||
(lambda: (yield))(),
|
||||
|
|
|
@ -39,6 +39,9 @@ Library
|
|||
- Issue #24134: assertRaises(), assertRaisesRegex(), assertWarns() and
|
||||
assertWarnsRegex() checks are not longer successful if the callable is None.
|
||||
|
||||
- Issue #24018: Add a collections.Generator abstract base class.
|
||||
Contributed by Stefan Behnel.
|
||||
|
||||
- Issue #23880: Tkinter's getint() and getdouble() now support Tcl_Obj.
|
||||
Tkinter's getdouble() now supports any numbers (in particular int).
|
||||
|
||||
|
|
Loading…
Reference in New Issue