Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bd60e8d

Browse files
committed
Issue #24018: Add a collections.Generator abstract base class.
1 parent dae2ef1 commit bd60e8d

4 files changed

Lines changed: 145 additions & 2 deletions

File tree

Doc/library/collections.abc.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ ABC Inherits from Abstract Methods Mixin
4040
:class:`Hashable` ``__hash__``
4141
:class:`Iterable` ``__iter__``
4242
:class:`Iterator` :class:`Iterable` ``__next__`` ``__iter__``
43+
:class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
4344
:class:`Sized` ``__len__``
4445
:class:`Callable` ``__call__``
4546

@@ -102,6 +103,15 @@ ABC Inherits from Abstract Methods Mixin
102103
:meth:`~iterator.__next__` methods. See also the definition of
103104
:term:`iterator`.
104105

106+
.. class:: Generator
107+
108+
ABC for generator classes that implement the protocol defined in
109+
:pep:`342` that extends iterators with the :meth:`~generator.send`,
110+
:meth:`~generator.throw` and :meth:`~generator.close` methods.
111+
See also the definition of :term:`generator`.
112+
113+
.. versionadded:: 3.5
114+
105115
.. class:: Sequence
106116
MutableSequence
107117

Lib/_collections_abc.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from abc import ABCMeta, abstractmethod
1010
import sys
1111

12-
__all__ = ["Hashable", "Iterable", "Iterator",
12+
__all__ = ["Hashable", "Iterable", "Iterator", "Generator",
1313
"Sized", "Container", "Callable",
1414
"Set", "MutableSet",
1515
"Mapping", "MutableMapping",
@@ -50,6 +50,7 @@
5050
dict_items = type({}.items())
5151
## misc ##
5252
mappingproxy = type(type.__dict__)
53+
generator = type((lambda: (yield))())
5354

5455

5556
### ONE-TRICK PONIES ###
@@ -124,6 +125,64 @@ def __subclasshook__(cls, C):
124125
Iterator.register(tuple_iterator)
125126
Iterator.register(zip_iterator)
126127

128+
129+
class Generator(Iterator):
130+
131+
__slots__ = ()
132+
133+
def __next__(self):
134+
"""Return the next item from the generator.
135+
When exhausted, raise StopIteration.
136+
"""
137+
return self.send(None)
138+
139+
@abstractmethod
140+
def send(self, value):
141+
"""Send a value into the generator.
142+
Return next yielded value or raise StopIteration.
143+
"""
144+
raise StopIteration
145+
146+
@abstractmethod
147+
def throw(self, typ, val=None, tb=None):
148+
"""Raise an exception in the generator.
149+
Return next yielded value or raise StopIteration.
150+
"""
151+
if val is None:
152+
if tb is None:
153+
raise typ
154+
val = typ()
155+
if tb is not None:
156+
val = val.with_traceback(tb)
157+
raise val
158+
159+
def close(self):
160+
"""Raise GeneratorExit inside generator.
161+
"""
162+
try:
163+
self.throw(GeneratorExit)
164+
except (GeneratorExit, StopIteration):
165+
pass
166+
else:
167+
raise RuntimeError("generator ignored GeneratorExit")
168+
169+
@classmethod
170+
def __subclasshook__(cls, C):
171+
if cls is Generator:
172+
mro = C.__mro__
173+
for method in ('__iter__', '__next__', 'send', 'throw', 'close'):
174+
for base in mro:
175+
if method in base.__dict__:
176+
break
177+
else:
178+
return NotImplemented
179+
return True
180+
return NotImplemented
181+
182+
183+
Generator.register(generator)
184+
185+
127186
class Sized(metaclass=ABCMeta):
128187

129188
__slots__ = ()

Lib/test/test_collections.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from collections import UserDict
1515
from collections import ChainMap
1616
from collections import deque
17-
from collections.abc import Hashable, Iterable, Iterator
17+
from collections.abc import Hashable, Iterable, Iterator, Generator
1818
from collections.abc import Sized, Container, Callable
1919
from collections.abc import Set, MutableSet
2020
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView
@@ -522,6 +522,77 @@ def __next__(self):
522522
return
523523
self.assertNotIsInstance(NextOnly(), Iterator)
524524

525+
def test_Generator(self):
526+
class NonGen1:
527+
def __iter__(self): return self
528+
def __next__(self): return None
529+
def close(self): pass
530+
def throw(self, typ, val=None, tb=None): pass
531+
532+
class NonGen2:
533+
def __iter__(self): return self
534+
def __next__(self): return None
535+
def close(self): pass
536+
def send(self, value): return value
537+
538+
class NonGen3:
539+
def close(self): pass
540+
def send(self, value): return value
541+
def throw(self, typ, val=None, tb=None): pass
542+
543+
non_samples = [
544+
None, 42, 3.14, 1j, b"", "", (), [], {}, set(),
545+
iter(()), iter([]), NonGen1(), NonGen2(), NonGen3()]
546+
for x in non_samples:
547+
self.assertNotIsInstance(x, Generator)
548+
self.assertFalse(issubclass(type(x), Generator), repr(type(x)))
549+
550+
class Gen:
551+
def __iter__(self): return self
552+
def __next__(self): return None
553+
def close(self): pass
554+
def send(self, value): return value
555+
def throw(self, typ, val=None, tb=None): pass
556+
557+
class MinimalGen(Generator):
558+
def send(self, value):
559+
return value
560+
def throw(self, typ, val=None, tb=None):
561+
super().throw(typ, val, tb)
562+
563+
def gen():
564+
yield 1
565+
566+
samples = [gen(), (lambda: (yield))(), Gen(), MinimalGen()]
567+
for x in samples:
568+
self.assertIsInstance(x, Iterator)
569+
self.assertIsInstance(x, Generator)
570+
self.assertTrue(issubclass(type(x), Generator), repr(type(x)))
571+
self.validate_abstract_methods(Generator, 'send', 'throw')
572+
573+
# mixin tests
574+
mgen = MinimalGen()
575+
self.assertIs(mgen, iter(mgen))
576+
self.assertIs(mgen.send(None), next(mgen))
577+
self.assertEqual(2, mgen.send(2))
578+
self.assertIsNone(mgen.close())
579+
self.assertRaises(ValueError, mgen.throw, ValueError)
580+
self.assertRaisesRegex(ValueError, "^huhu$",
581+
mgen.throw, ValueError, ValueError("huhu"))
582+
self.assertRaises(StopIteration, mgen.throw, StopIteration())
583+
584+
class FailOnClose(Generator):
585+
def send(self, value): return value
586+
def throw(self, *args): raise ValueError
587+
588+
self.assertRaises(ValueError, FailOnClose().close)
589+
590+
class IgnoreGeneratorExit(Generator):
591+
def send(self, value): return value
592+
def throw(self, *args): pass
593+
594+
self.assertRaises(RuntimeError, IgnoreGeneratorExit().close)
595+
525596
def test_Sized(self):
526597
non_samples = [None, 42, 3.14, 1j,
527598
(lambda: (yield))(),

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Library
3939
- Issue #24134: assertRaises(), assertRaisesRegex(), assertWarns() and
4040
assertWarnsRegex() checks are not longer successful if the callable is None.
4141

42+
- Issue #24018: Add a collections.Generator abstract base class.
43+
Contributed by Stefan Behnel.
44+
4245
- Issue #23880: Tkinter's getint() and getdouble() now support Tcl_Obj.
4346
Tkinter's getdouble() now supports any numbers (in particular int).
4447

0 commit comments

Comments
 (0)