diff options
author | Raymond Hettinger <python@rcn.com> | 2015-05-09 05:07:23 (GMT) |
---|---|---|
committer | Raymond Hettinger <python@rcn.com> | 2015-05-09 05:07:23 (GMT) |
commit | bd60e8dece89440ebdc80a19b2217d5ba2515124 (patch) | |
tree | 97cf2f7e3802aede1de63eb304cefcf55791077e | |
parent | dae2ef1cfad60b149370b4012aa48bea2dd27445 (diff) | |
download | cpython-bd60e8dece89440ebdc80a19b2217d5ba2515124.zip cpython-bd60e8dece89440ebdc80a19b2217d5ba2515124.tar.gz cpython-bd60e8dece89440ebdc80a19b2217d5ba2515124.tar.bz2 |
Issue #24018: Add a collections.Generator abstract base class.
-rw-r--r-- | Doc/library/collections.abc.rst | 10 | ||||
-rw-r--r-- | Lib/_collections_abc.py | 61 | ||||
-rw-r--r-- | Lib/test/test_collections.py | 73 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
4 files changed, 145 insertions, 2 deletions
diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index 99c4311..8a71259 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -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 diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 3d3f07b..cb87e6b 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -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__ = () diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 958fb62..5b2e81f 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -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). |