检查 Python 函数是否引用了任何超出范围的内容

Checking if a Python function referenced anything out-of-scope

我正在探索在 Python 中可以做什么,最近遇到了这个问题:在 运行 一个函数之后,是否有可能以编程方式确定它是否引用了任何超出-范围?例如:

import module1

y = 1

def foo1(x):
    return y + x  # yes - it has referenced 'y' which is out of foo1 scope

def foo2(x):
    return module1.function1(x)  # yes - it has referenced 'module1' which is out of foo2 scope

def foo3(x):
    return x*x  # no - it has only referenced 'x' which is an input to foo3, so the code executed within the scope of foo3

是否有针对此的一些反射/分析工具?也许这可以通过 'trace' 模块以某种方式实现?

检查内置 locals() 函数。这将 return 本地范围内变量的字典。

global_var = "Hello"

def foo():
 local_var = "world"
 if "local_var" in locals(): print(c)
 if "global_var" in locals(): print(global_var)

>>> foo()
world

您可以使用 inspect.getclosurevars:

Get the mapping of external name references in a Python function or method func to their current values. A named tuple ClosureVars(nonlocals, globals, builtins, unbound) is returned. nonlocals maps referenced names to lexical closure variables, globals to the function’s module globals and builtins to the builtins visible from the function body. unbound is the set of names referenced in the function that could not be resolved at all given the current module globals and builtins.

让我们看看它 returns 你的例子:

案例一:

def foo1(x):
    return y + x  # yes - it has referenced 'y' which is out of foo1 scope

inspect.getclosurevars(foo1)
# ClosureVars(nonlocals={}, globals={'y': 1}, builtins={}, unbound=set())

这里它检测到您正在全局范围内使用变量。

案例二:

import colorsys
def foo2(x):
    return colorsys.rgb_to_hsv(*x) # yes - it has referenced 'colorsys' which is out of foo2 scope

inspect.getclosurevars(foo2)
# ClosureVars(nonlocals={}, globals={'colorsys': <module 'colorsys' from '...\lib\colorsys.py'>}, builtins={}, unbound={'rgb_to_hsv'})

这里检测到您正在使用模块的功能。

案例三:

def foo3(x):
    return x*x  # no - it has only referenced 'x' which is an input to foo3, so the code executed within the scope of foo3

inspect.getclosurevars(foo3)
# ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())

这里我们看到“没有异常”。

因此,我们可以将此过程包装在一个函数中,该函数会查看是否有 any 个字段(内置函数除外;即 foo 可以使用 abs 没问题)非空:

import inspect

def references_sth_out_of_scope(fun):
    closure_vars = inspect.getclosurevars(fun)
    referenced = any(getattr(closure_vars, field) for field in closure_vars._fields if field != "builtins")
    return referenced

用法:

>>> references_sth_out_of_scope(foo1)
True

>>> references_sth_out_of_scope(foo2)
True

>>> references_sth_out_of_scope(foo3)
False

>>> references_sth_out_of_scope(references_sth_out_of_scope) # :)
True