Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm exploring what's possible to do in Python and recently came across this question: after running a function, is it possible to programmatically determine whether it has referenced anything out-of-scope? For example:

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

Are there some reflection / analysis tools for this? Maybe this could be achieved with the 'trace' module somehow?

like image 981
Petras Purlys Avatar asked Oct 27 '25 12:10

Petras Purlys


1 Answers

You can use 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.

Let's see what it returns for your examples:

Case 1:

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())

Here it detected you are using variable in the global scope.

Case 2:

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'})

Here it detected you are using a module's function.

Case 3:

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())

Here we see "nothing unusual".

Therefore, we can wrap this procedure in a function which looks if any of the fields (except for built-ins; i.e. foo can use abs and no problem) is non-empty:

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

Usage:

>>> 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
like image 99
Mustafa Aydın Avatar answered Oct 29 '25 01:10

Mustafa Aydın



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!