Skip to content

Commit d5b4f1b

Browse files
authored
bpo-34983: Expose symtable.Symbol.is_nonlocal() in the symtable module (GH-9872)
The symbol table was not exposing functionality to query the nonlocal symbols in a function or to check if a particular symbol is nonlocal.
1 parent 6395844 commit d5b4f1b

File tree

5 files changed

+36
-3
lines changed

5 files changed

+36
-3
lines changed

Doc/library/symtable.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ Examining Symbol Tables
105105

106106
Return a tuple containing names of globals in this function.
107107

108+
.. method:: get_nonlocals()
109+
110+
Return a tuple containing names of nonlocals in this function.
111+
108112
.. method:: get_frees()
109113

110114
Return a tuple containing names of free variables in this function.
@@ -144,6 +148,10 @@ Examining Symbol Tables
144148

145149
Return ``True`` if the symbol is global.
146150

151+
.. method:: is_nonlocal()
152+
153+
Return ``True`` if the symbol is nonlocal.
154+
147155
.. method:: is_declared_global()
148156

149157
Return ``True`` if the symbol is declared global with a global statement.

Lib/symtable.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Interface to the compiler's internal symbol tables"""
22

33
import _symtable
4-
from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM,
4+
from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM,
55
DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE,
66
LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL)
77

@@ -117,6 +117,7 @@ class Function(SymbolTable):
117117
__locals = None
118118
__frees = None
119119
__globals = None
120+
__nonlocals = None
120121

121122
def __idents_matching(self, test_func):
122123
return tuple(ident for ident in self.get_identifiers()
@@ -141,6 +142,11 @@ def get_globals(self):
141142
self.__globals = self.__idents_matching(test)
142143
return self.__globals
143144

145+
def get_nonlocals(self):
146+
if self.__nonlocals is None:
147+
self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL)
148+
return self.__nonlocals
149+
144150
def get_frees(self):
145151
if self.__frees is None:
146152
is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
@@ -184,6 +190,9 @@ def is_parameter(self):
184190
def is_global(self):
185191
return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT))
186192

193+
def is_nonlocal(self):
194+
return bool(self.__flags & DEF_NONLOCAL)
195+
187196
def is_declared_global(self):
188197
return bool(self.__scope == GLOBAL_EXPLICIT)
189198

Lib/test/test_symtable.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
1212
glob = 42
13+
some_var = 12
1314
1415
class Mine:
1516
instance_var = 24
@@ -19,10 +20,15 @@ def a_method(p1, p2):
1920
def spam(a, b, *var, **kw):
2021
global bar
2122
bar = 47
23+
some_var = 10
2224
x = 23
2325
glob
2426
def internal():
2527
return x
28+
def other_internal():
29+
nonlocal some_var
30+
some_var = 3
31+
return some_var
2632
return internal
2733
2834
def foo():
@@ -47,6 +53,7 @@ class SymtableTest(unittest.TestCase):
4753
a_method = find_block(Mine, "a_method")
4854
spam = find_block(top, "spam")
4955
internal = find_block(spam, "internal")
56+
other_internal = find_block(spam, "other_internal")
5057
foo = find_block(top, "foo")
5158

5259
def test_type(self):
@@ -75,12 +82,12 @@ def test_children(self):
7582

7683
def test_lineno(self):
7784
self.assertEqual(self.top.get_lineno(), 0)
78-
self.assertEqual(self.spam.get_lineno(), 11)
85+
self.assertEqual(self.spam.get_lineno(), 12)
7986

8087
def test_function_info(self):
8188
func = self.spam
8289
self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"])
83-
expected = ["a", "b", "internal", "kw", "var", "x"]
90+
expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x']
8491
self.assertEqual(sorted(func.get_locals()), expected)
8592
self.assertEqual(sorted(func.get_globals()), ["bar", "glob"])
8693
self.assertEqual(self.internal.get_frees(), ("x",))
@@ -93,6 +100,12 @@ def test_globals(self):
93100
self.assertFalse(self.internal.lookup("x").is_global())
94101
self.assertFalse(self.Mine.lookup("instance_var").is_global())
95102

103+
def test_nonlocal(self):
104+
self.assertFalse(self.spam.lookup("some_var").is_nonlocal())
105+
self.assertTrue(self.other_internal.lookup("some_var").is_nonlocal())
106+
expected = ("some_var",)
107+
self.assertEqual(self.other_internal.get_nonlocals(), expected)
108+
96109
def test_local(self):
97110
self.assertTrue(self.spam.lookup("x").is_local())
98111
self.assertFalse(self.internal.lookup("x").is_local())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Expose :meth:`symtable.Symbol.is_nonlocal` in the symtable module. Patch by
2+
Pablo Galindo.

Modules/symtablemodule.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ PyInit__symtable(void)
8484
return NULL;
8585
PyModule_AddIntMacro(m, USE);
8686
PyModule_AddIntMacro(m, DEF_GLOBAL);
87+
PyModule_AddIntMacro(m, DEF_NONLOCAL);
8788
PyModule_AddIntMacro(m, DEF_LOCAL);
8889
PyModule_AddIntMacro(m, DEF_PARAM);
8990
PyModule_AddIntMacro(m, DEF_FREE);

0 commit comments

Comments
 (0)