>>> list("abc")
['a', 'b', 'c']
>>> list = 42
>>> list("xyz")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
How do i find such bugs if one has accidentally assigned values to class-names and function definitions? I wanted to use AST but not really sure how do i do it?
One approach is to extract ast
nodes for constructs that could contain builtin shadowing; for example, assignments, function signatures, for-loops, and comprehensions, and then check if any target names belonging to these ast
objects are builtins:
import warnings
import builtins, ast
def extract_names(node):
if isinstance(node, ast.Name):
yield node.id
elif isinstance(node, ast.arg):
yield node.arg
elif isinstance(node, list):
for i in node: yield from extract_names(i)
else:
for i in getattr(node, '_fields', []): yield from extract_names(getattr(node, i))
def log_shadowing(node, names):
for i in names:
if i in dir(builtins):
warnings.warn(f"On line {node.lineno}: shadowing of '{i}'")
def check_node(node):
if isinstance(node, ast.Assign):
log_shadowing(node, extract_names(node.targets))
if isinstance(node, ast.FunctionDef):
log_shadowing(node, [node.name, *extract_names(node.args)])
if isinstance(node, ast.For):
log_shadowing(node, extract_names(node.target))
if isinstance(node, (ast.ListComp, ast.SetComp, ast.DictComp)):
for i in node.generators:
log_shadowing(node, extract_names(i.target))
sample = """
list = 10
def f(a, b, dict, c):
pass
for set in range(10):
pass
r = [next for next in range(10)]
"""
for i in ast.walk(ast.parse(sample)):
check_node(i)
Output:
<stdin>:4: UserWarning: On line 2: shadowing of 'list'
<stdin>:4: UserWarning: On line 3: shadowing of 'dict'
<stdin>:4: UserWarning: On line 6: shadowing of 'set'
<stdin>:4: UserWarning: On line 9: shadowing of 'next'