From d5b4f1b5a064c0d858352100fcddb91c363afa51 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 20 Oct 2018 01:46:00 +0100 Subject: [PATCH] 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. --- Doc/library/symtable.rst | 8 ++++++++ Lib/symtable.py | 11 ++++++++++- Lib/test/test_symtable.py | 17 +++++++++++++++-- .../2018-10-14-17-26-41.bpo-34983.l8XaZd.rst | 2 ++ Modules/symtablemodule.c | 1 + 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index ba2caff589..7c6ac4dccf 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -105,6 +105,10 @@ Examining Symbol Tables Return a tuple containing names of globals in this function. + .. method:: get_nonlocals() + + Return a tuple containing names of nonlocals in this function. + .. method:: get_frees() Return a tuple containing names of free variables in this function. @@ -144,6 +148,10 @@ Examining Symbol Tables Return ``True`` if the symbol is global. + .. method:: is_nonlocal() + + Return ``True`` if the symbol is nonlocal. + .. method:: is_declared_global() Return ``True`` if the symbol is declared global with a global statement. diff --git a/Lib/symtable.py b/Lib/symtable.py index c7627a6ef6..5bea7cf615 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -1,7 +1,7 @@ """Interface to the compiler's internal symbol tables""" import _symtable -from _symtable import (USE, DEF_GLOBAL, DEF_LOCAL, DEF_PARAM, +from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM, DEF_IMPORT, DEF_BOUND, DEF_ANNOT, SCOPE_OFF, SCOPE_MASK, FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL) @@ -117,6 +117,7 @@ class Function(SymbolTable): __locals = None __frees = None __globals = None + __nonlocals = None def __idents_matching(self, test_func): return tuple(ident for ident in self.get_identifiers() @@ -141,6 +142,11 @@ class Function(SymbolTable): self.__globals = self.__idents_matching(test) return self.__globals + def get_nonlocals(self): + if self.__nonlocals is None: + self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL) + return self.__nonlocals + def get_frees(self): if self.__frees is None: is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE @@ -184,6 +190,9 @@ class Symbol(object): def is_global(self): return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)) + def is_nonlocal(self): + return bool(self.__flags & DEF_NONLOCAL) + def is_declared_global(self): return bool(self.__scope == GLOBAL_EXPLICIT) diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 8d76f6fe45..0a1cb8d5b4 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -10,6 +10,7 @@ TEST_CODE = """ import sys glob = 42 +some_var = 12 class Mine: instance_var = 24 @@ -19,10 +20,15 @@ class Mine: def spam(a, b, *var, **kw): global bar bar = 47 + some_var = 10 x = 23 glob def internal(): return x + def other_internal(): + nonlocal some_var + some_var = 3 + return some_var return internal def foo(): @@ -47,6 +53,7 @@ class SymtableTest(unittest.TestCase): a_method = find_block(Mine, "a_method") spam = find_block(top, "spam") internal = find_block(spam, "internal") + other_internal = find_block(spam, "other_internal") foo = find_block(top, "foo") def test_type(self): @@ -75,12 +82,12 @@ class SymtableTest(unittest.TestCase): def test_lineno(self): self.assertEqual(self.top.get_lineno(), 0) - self.assertEqual(self.spam.get_lineno(), 11) + self.assertEqual(self.spam.get_lineno(), 12) def test_function_info(self): func = self.spam self.assertEqual(sorted(func.get_parameters()), ["a", "b", "kw", "var"]) - expected = ["a", "b", "internal", "kw", "var", "x"] + expected = ['a', 'b', 'internal', 'kw', 'other_internal', 'some_var', 'var', 'x'] self.assertEqual(sorted(func.get_locals()), expected) self.assertEqual(sorted(func.get_globals()), ["bar", "glob"]) self.assertEqual(self.internal.get_frees(), ("x",)) @@ -93,6 +100,12 @@ class SymtableTest(unittest.TestCase): self.assertFalse(self.internal.lookup("x").is_global()) self.assertFalse(self.Mine.lookup("instance_var").is_global()) + def test_nonlocal(self): + self.assertFalse(self.spam.lookup("some_var").is_nonlocal()) + self.assertTrue(self.other_internal.lookup("some_var").is_nonlocal()) + expected = ("some_var",) + self.assertEqual(self.other_internal.get_nonlocals(), expected) + def test_local(self): self.assertTrue(self.spam.lookup("x").is_local()) self.assertFalse(self.internal.lookup("x").is_local()) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst b/Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst new file mode 100644 index 0000000000..dd76b63db1 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-10-14-17-26-41.bpo-34983.l8XaZd.rst @@ -0,0 +1,2 @@ +Expose :meth:`symtable.Symbol.is_nonlocal` in the symtable module. Patch by +Pablo Galindo. diff --git a/Modules/symtablemodule.c b/Modules/symtablemodule.c index 810f88da48..e8d2f5b582 100644 --- a/Modules/symtablemodule.c +++ b/Modules/symtablemodule.c @@ -84,6 +84,7 @@ PyInit__symtable(void) return NULL; PyModule_AddIntMacro(m, USE); PyModule_AddIntMacro(m, DEF_GLOBAL); + PyModule_AddIntMacro(m, DEF_NONLOCAL); PyModule_AddIntMacro(m, DEF_LOCAL); PyModule_AddIntMacro(m, DEF_PARAM); PyModule_AddIntMacro(m, DEF_FREE); -- 2.40.0