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

Skip to content

Commit 04e2e3f

Browse files
committed
Close #15153: Added inspect.getgeneratorlocals to simplify whitebox testing of generator state updates
1 parent 766e622 commit 04e2e3f

5 files changed

Lines changed: 98 additions & 0 deletions

File tree

Doc/library/inspect.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,27 @@ generator to be determined easily.
676676
* GEN_CLOSED: Execution has completed.
677677

678678
.. versionadded:: 3.2
679+
680+
The current internal state of the generator can also be queried. This is
681+
mostly useful for testing purposes, to ensure that internal state is being
682+
updated as expected:
683+
684+
.. function:: getgeneratorlocals(generator)
685+
686+
Get the mapping of live local variables in *generator* to their current
687+
values. A dictionary is returned that maps from variable names to values.
688+
This is the equivalent of calling :func:`locals` in the body of the
689+
generator, and all the same caveats apply.
690+
691+
If *generator* is a :term:`generator` with no currently associated frame,
692+
then an empty dictionary is returned. :exc:`TypeError` is raised if
693+
*generator* is not a Python generator object.
694+
695+
.. impl-detail::
696+
697+
This function relies on the generator exposing a Python stack frame
698+
for introspection, which isn't guaranteed to be the case in all
699+
implementations of Python. In such cases, this function will always
700+
return an empty dictionary.
701+
702+
.. versionadded:: 3.3

Doc/whatsnew/3.3.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,13 @@ state when testing code that relies on stateful closures.
10371037

10381038
(Contributed by Meador Inge and Nick Coghlan in :issue:`13062`)
10391039

1040+
A new :func:`~inspect.getgeneratorlocals` function has been added. This
1041+
function reports the current binding of local variables in the generator's
1042+
stack frame, making it easier to verify correct internal state when testing
1043+
generators.
1044+
1045+
(Contributed by Meador Inge in :issue:`15153`)
1046+
10401047
io
10411048
--
10421049

Lib/inspect.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,8 @@ def getattr_static(obj, attr, default=_sentinel):
12591259
raise AttributeError(attr)
12601260

12611261

1262+
# ------------------------------------------------ generator introspection
1263+
12621264
GEN_CREATED = 'GEN_CREATED'
12631265
GEN_RUNNING = 'GEN_RUNNING'
12641266
GEN_SUSPENDED = 'GEN_SUSPENDED'
@@ -1282,6 +1284,22 @@ def getgeneratorstate(generator):
12821284
return GEN_SUSPENDED
12831285

12841286

1287+
def getgeneratorlocals(generator):
1288+
"""
1289+
Get the mapping of generator local variables to their current values.
1290+
1291+
A dict is returned, with the keys the local variable names and values the
1292+
bound values."""
1293+
1294+
if not isgenerator(generator):
1295+
raise TypeError("'{!r}' is not a Python generator".format(generator))
1296+
1297+
frame = getattr(generator, "gi_frame", None)
1298+
if frame is not None:
1299+
return generator.gi_frame.f_locals
1300+
else:
1301+
return {}
1302+
12851303
###############################################################################
12861304
### Function Signature Object (PEP 362)
12871305
###############################################################################

Lib/test/test_inspect.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,52 @@ def test_easy_debugging(self):
12711271
self.assertIn(name, repr(state))
12721272
self.assertIn(name, str(state))
12731273

1274+
def test_getgeneratorlocals(self):
1275+
def each(lst, a=None):
1276+
b=(1, 2, 3)
1277+
for v in lst:
1278+
if v == 3:
1279+
c = 12
1280+
yield v
1281+
1282+
numbers = each([1, 2, 3])
1283+
self.assertEqual(inspect.getgeneratorlocals(numbers),
1284+
{'a': None, 'lst': [1, 2, 3]})
1285+
next(numbers)
1286+
self.assertEqual(inspect.getgeneratorlocals(numbers),
1287+
{'a': None, 'lst': [1, 2, 3], 'v': 1,
1288+
'b': (1, 2, 3)})
1289+
next(numbers)
1290+
self.assertEqual(inspect.getgeneratorlocals(numbers),
1291+
{'a': None, 'lst': [1, 2, 3], 'v': 2,
1292+
'b': (1, 2, 3)})
1293+
next(numbers)
1294+
self.assertEqual(inspect.getgeneratorlocals(numbers),
1295+
{'a': None, 'lst': [1, 2, 3], 'v': 3,
1296+
'b': (1, 2, 3), 'c': 12})
1297+
try:
1298+
next(numbers)
1299+
except StopIteration:
1300+
pass
1301+
self.assertEqual(inspect.getgeneratorlocals(numbers), {})
1302+
1303+
def test_getgeneratorlocals_empty(self):
1304+
def yield_one():
1305+
yield 1
1306+
one = yield_one()
1307+
self.assertEqual(inspect.getgeneratorlocals(one), {})
1308+
try:
1309+
next(one)
1310+
except StopIteration:
1311+
pass
1312+
self.assertEqual(inspect.getgeneratorlocals(one), {})
1313+
1314+
def test_getgeneratorlocals_error(self):
1315+
self.assertRaises(TypeError, inspect.getgeneratorlocals, 1)
1316+
self.assertRaises(TypeError, inspect.getgeneratorlocals, lambda x: True)
1317+
self.assertRaises(TypeError, inspect.getgeneratorlocals, set)
1318+
self.assertRaises(TypeError, inspect.getgeneratorlocals, (2,3))
1319+
12741320

12751321
class TestSignatureObject(unittest.TestCase):
12761322
@staticmethod

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ Core and Builtins
4040
Library
4141
-------
4242

43+
- Issue #15153: Added inspect.getgeneratorlocals to simplify white box
44+
testing of generator state updates
45+
4346
- Issue #13062: Added inspect.getclosurevars to simplify testing stateful
4447
closures
4548

0 commit comments

Comments
 (0)