summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaymond Hettinger <python@rcn.com>2015-05-09 05:07:23 (GMT)
committerRaymond Hettinger <python@rcn.com>2015-05-09 05:07:23 (GMT)
commitbd60e8dece89440ebdc80a19b2217d5ba2515124 (patch)
tree97cf2f7e3802aede1de63eb304cefcf55791077e
parentdae2ef1cfad60b149370b4012aa48bea2dd27445 (diff)
downloadcpython-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.rst10
-rw-r--r--Lib/_collections_abc.py61
-rw-r--r--Lib/test/test_collections.py73
-rw-r--r--Misc/NEWS3
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))(),
diff --git a/Misc/NEWS b/Misc/NEWS
index 13dd8c3..ac065f7 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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).