From e0f04659cd717a031b42dcfd43ca07824b24f9b2 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sun, 21 Nov 2010 03:44:04 +0000 Subject: [PATCH] Issue #10220: Add inspect.getgeneratorstate(). Initial patch by Rodolpho Eckhardt --- Doc/library/inspect.rst | 22 +++++++++++++++++++ Doc/whatsnew/3.2.rst | 8 +++++++ Lib/inspect.py | 20 +++++++++++++++++ Lib/test/test_inspect.py | 47 +++++++++++++++++++++++++++++++++++++++- Misc/NEWS | 3 +++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 9a068dab312..810a95b93bc 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -620,3 +620,25 @@ code execution:: # in which case the descriptor itself will # have to do pass + +Current State of a Generator +---------------------------- + +When implementing coroutine schedulers and for other advanced uses of +generators, it is useful to determine whether a generator is currently +executing, is waiting to start or resume or execution, or has already +terminated. func:`getgeneratorstate` allows the current state of a +generator to be determined easily. + +.. function:: getgeneratorstate(generator) + + Get current state of a generator-iterator. + + Possible states are: + GEN_CREATED: Waiting to start execution. + GEN_RUNNING: Currently being executed by the interpreter. + GEN_SUSPENDED: Currently suspended at a yield expression. + GEN_CLOSED: Execution has completed. + + + diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 1bad5447394..dd47129f442 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -554,6 +554,14 @@ New, Improved, and Deprecated Modules (Contributed by R. David Murray, :issue:`10321`.) +* The :mod:`inspect` module has a new function :func:`getgenatorstate` + to easily identify the current state of a generator as one of + ``GEN_CREATED``, ``GEN_RUNNING``, ``GEN_SUSPENDED`` or ``GEN_CLOSED``. + + (Contributed by Rodolpho Eckhardt and Nick Coghlan, :issue:`10220`.) + +.. XXX: Mention inspect.getattr_static (Michael Foord) + Multi-threading =============== diff --git a/Lib/inspect.py b/Lib/inspect.py index 241cd08bffd..e410dba61f5 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1128,3 +1128,23 @@ def getattr_static(obj, attr, default=_sentinel): if default is not _sentinel: return default raise AttributeError(attr) + + +GEN_CREATED, GEN_RUNNING, GEN_SUSPENDED, GEN_CLOSED = range(4) + +def getgeneratorstate(generator): + """Get current state of a generator-iterator. + + Possible states are: + GEN_CREATED: Waiting to start execution. + GEN_RUNNING: Currently being executed by the interpreter. + GEN_SUSPENDED: Currently suspended at a yield expression. + GEN_CLOSED: Execution has completed. + """ + if generator.gi_running: + return GEN_RUNNING + if generator.gi_frame is None: + return GEN_CLOSED + if generator.gi_frame.f_lasti == -1: + return GEN_CREATED + return GEN_SUSPENDED diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index cba6f135da3..97c47ac8df4 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -887,12 +887,57 @@ class TestGetattrStatic(unittest.TestCase): self.assertEqual(inspect.getattr_static(Something, 'foo'), 3) +class TestGetGeneratorState(unittest.TestCase): + + def setUp(self): + def number_generator(): + for number in range(5): + yield number + self.generator = number_generator() + + def _generatorstate(self): + return inspect.getgeneratorstate(self.generator) + + def test_created(self): + self.assertEqual(self._generatorstate(), inspect.GEN_CREATED) + + def test_suspended(self): + next(self.generator) + self.assertEqual(self._generatorstate(), inspect.GEN_SUSPENDED) + + def test_closed_after_exhaustion(self): + for i in self.generator: + pass + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + self.generator.throw(RuntimeError) + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_running(self): + # As mentioned on issue #10220, checking for the RUNNING state only + # makes sense inside the generator itself. + # The following generator checks for this by using the closure's + # reference to self and the generator state checking helper method + def running_check_generator(): + for number in range(5): + self.assertEqual(self._generatorstate(), inspect.GEN_RUNNING) + yield number + self.assertEqual(self._generatorstate(), inspect.GEN_RUNNING) + self.generator = running_check_generator() + # Running up to the first yield + next(self.generator) + # Running after the first yield + next(self.generator) + + def test_main(): run_unittest( TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, - TestGetcallargsUnboundMethods, TestGetattrStatic + TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState ) if __name__ == "__main__": diff --git a/Misc/NEWS b/Misc/NEWS index 04d624c68d2..ca00d9b0050 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -32,6 +32,9 @@ Core and Builtins Library ------- +- Issue #10220: Added inspect.getgeneratorstate. Initial patch by + Rodolpho Eckhardt. + - Issue #10453: compileall now uses argparse instead of getopt, and thus provides clean output when called with '-h'.