Return ``True`` if the symbol is a type parameter.
+ .. versionadded:: 3.14
+
.. method:: is_global()
Return ``True`` if the symbol is global.
Return ``True`` if the symbol is referenced in its block, but not assigned
to.
+ .. method:: is_free_class()
+
+ Return *True* if a class-scoped symbol is free from
+ the perspective of a method.
+
+ Consider the following example::
+
+ def f():
+ x = 1 # function-scoped
+ class C:
+ x = 2 # class-scoped
+ def method(self):
+ return x
+
+ In this example, the class-scoped symbol ``x`` is considered to
+ be free from the perspective of ``C.method``, thereby allowing
+ the latter to return *1* at runtime and not *2*.
+
+ .. versionadded:: 3.14
+
.. method:: is_assigned()
Return ``True`` if the symbol is assigned to in its block.
+ .. method:: is_comp_iter()
+
+ Return ``True`` if the symbol is a comprehension iteration variable.
+
+ .. versionadded:: 3.14
+
+ .. method:: is_comp_cell()
+
+ Return ``True`` if the symbol is a cell in an inlined comprehension.
+
+ .. versionadded:: 3.14
+
.. method:: is_namespace()
Return ``True`` if name binding introduces new namespace.
by :func:`os.unsetenv`, or made outside Python in the same process.
(Contributed by Victor Stinner in :gh:`120057`.)
+symtable
+--------
+
+* Expose the following :class:`symtable.Symbol` methods:
+
+ * :meth:`~symtable.Symbol.is_free_class`
+ * :meth:`~symtable.Symbol.is_comp_iter`
+ * :meth:`~symtable.Symbol.is_comp_cell`
+
+ (Contributed by Bénédikt Tran in :gh:`120029`.)
+
Optimizations
=============
#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
/* GLOBAL_EXPLICIT and GLOBAL_IMPLICIT are used internally by the symbol
- table. GLOBAL is returned from PyST_GetScope() for either of them.
+ table. GLOBAL is returned from _PyST_GetScope() for either of them.
It is stored in ste_symbols at bits 13-16.
*/
#define SCOPE_OFFSET 12
from _symtable import (
USE,
DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL,
- DEF_PARAM, DEF_TYPE_PARAM, DEF_IMPORT, DEF_BOUND, DEF_ANNOT,
+ DEF_PARAM, DEF_TYPE_PARAM,
+ DEF_FREE_CLASS,
+ DEF_IMPORT, DEF_BOUND, DEF_ANNOT,
+ DEF_COMP_ITER, DEF_COMP_CELL,
SCOPE_OFF, SCOPE_MASK,
FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL
)
for st in self._table.children]
+def _get_scope(flags): # like _PyST_GetScope()
+ return (flags >> SCOPE_OFF) & SCOPE_MASK
+
+
class Function(SymbolTable):
# Default values for instance variables
"""
if self.__locals is None:
locs = (LOCAL, CELL)
- test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs
+ test = lambda x: _get_scope(x) in locs
self.__locals = self.__idents_matching(test)
return self.__locals
"""
if self.__globals is None:
glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
- test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
+ test = lambda x: _get_scope(x) in glob
self.__globals = self.__idents_matching(test)
return self.__globals
"""Return a tuple of free variables in the function.
"""
if self.__frees is None:
- is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
+ is_free = lambda x: _get_scope(x) == FREE
self.__frees = self.__idents_matching(is_free)
return self.__frees
def __init__(self, name, flags, namespaces=None, *, module_scope=False):
self.__name = name
self.__flags = flags
- self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
+ self.__scope = _get_scope(flags)
self.__namespaces = namespaces or ()
self.__module_scope = module_scope
"""
return bool(self.__scope == FREE)
+ def is_free_class(self):
+ """Return *True* if a class-scoped symbol is free from
+ the perspective of a method."""
+ return bool(self.__flags & DEF_FREE_CLASS)
+
def is_imported(self):
"""Return *True* if the symbol is created from
an import statement.
"""Return *True* if a symbol is assigned to."""
return bool(self.__flags & DEF_LOCAL)
+ def is_comp_iter(self):
+ """Return *True* if the symbol is a comprehension iteration variable.
+ """
+ return bool(self.__flags & DEF_COMP_ITER)
+
+ def is_comp_cell(self):
+ """Return *True* if the symbol is a cell in an inlined comprehension.
+ """
+ return bool(self.__flags & DEF_COMP_CELL)
+
def is_namespace(self):
"""Returns *True* if name binding introduces new namespace.
self.assertEqual(repr(self.GenericMine.lookup("T")),
"<symbol 'T': LOCAL, DEF_LOCAL|DEF_TYPE_PARAM>")
+ st1 = symtable.symtable("[x for x in [1]]", "?", "exec")
+ self.assertEqual(repr(st1.lookup("x")),
+ "<symbol 'x': LOCAL, USE|DEF_LOCAL|DEF_COMP_ITER>")
+
+ st2 = symtable.symtable("[(lambda: x) for x in [1]]", "?", "exec")
+ self.assertEqual(repr(st2.lookup("x")),
+ "<symbol 'x': CELL, DEF_LOCAL|DEF_COMP_ITER|DEF_COMP_CELL>")
+
+ st3 = symtable.symtable("def f():\n"
+ " x = 1\n"
+ " class A:\n"
+ " x = 2\n"
+ " def method():\n"
+ " return x\n",
+ "?", "exec")
+ # child 0 is for __annotate__
+ func_f = st3.get_children()[1]
+ class_A = func_f.get_children()[0]
+ self.assertEqual(repr(class_A.lookup('x')),
+ "<symbol 'x': LOCAL, DEF_LOCAL|DEF_FREE_CLASS>")
+
def test_symtable_entry_repr(self):
expected = f"<symtable entry top({self.top.get_id()}), line {self.top.get_lineno()}>"
self.assertEqual(repr(self.top._table), expected)
--- /dev/null
+Expose :class:`symtable.Symbol` methods :meth:`~symtable.Symbol.is_free_class`,
+:meth:`~symtable.Symbol.is_comp_iter` and :meth:`~symtable.Symbol.is_comp_cell`.
+Patch by Bénédikt Tran.
+
if (PyModule_AddIntMacro(m, DEF_IMPORT) < 0) return -1;
if (PyModule_AddIntMacro(m, DEF_BOUND) < 0) return -1;
if (PyModule_AddIntMacro(m, DEF_ANNOT) < 0) return -1;
+ if (PyModule_AddIntMacro(m, DEF_COMP_ITER) < 0) return -1;
+ if (PyModule_AddIntMacro(m, DEF_COMP_CELL) < 0) return -1;
if (PyModule_AddIntConstant(m, "TYPE_FUNCTION", FunctionBlock) < 0)
return -1;