'In Python, how can I get the global variables that are used in a function?

I'm trying to collect info on crashes and I am having trouble figuring out how to get the globals that are being used in the crashed function.

import inspect

fun = 222
other = "junk"

def test():
    global fun
    harold = 888 + fun
    try:
        harold/0
    except:
        frames = inspect.trace()
        print "Local variables:"
        print frames[0][0].f_locals

        print "All global variables, not what I want!"
        print frames[0][0].f_globals

test()

test() only uses "fun" but f_globals gives all the available globals. Is there some way to get just the globals that are being used by this function?



Solution 1:[1]

Check this out

a = 10

def test():
    global a
    a = 12
    b = 12

print "co_argcount = ",test.__code__.co_argcount
print "co_cellvars = ",test.__code__.co_cellvars
print "co_code = ",test.__code__.co_code
print "co_consts = ",test.__code__.co_consts
print "co_filename = ",test.__code__.co_filename
print "co_firstlineno = ",test.__code__.co_firstlineno
print "co_flags = ",test.__code__.co_flags
print "co_freevars = ",test.__code__.co_freevars
print "co_lnotab = ",test.__code__.co_lnotab
print "co_name = ",test.__code__.co_name
print "co_names = ",test.__code__.co_names
print "co_nlocals = ",test.__code__.co_nlocals
print "co_stacksize = ",test.__code__.co_stacksize
print "co_varnames = ",test.__code__.co_varnames

Solution 2:[2]

I needed that also myself. This is my solution. The non-fast path covers most cases you are probably interested in.

def iterGlobalsUsedInFunc(f, fast=False, loadsOnly=True):
    if hasattr(f, "func_code"): code = f.func_code
    else: code = f
    if fast:
        # co_names is the list of all names which are used.
        # These are mostly the globals. These are also attrib names, so these are more...
        for name in code.co_names:
            yield name
    else:
        # Use the disassembly. Note that this will still not
        # find dynamic lookups to `globals()`
        # (which is anyway not possible to detect always).
        import dis
        ops = ["LOAD_GLOBAL"]
        if not loadsOnly:
            ops += ["STORE_GLOBAL", "DELETE_GLOBAL"]
        ops = map(dis.opmap.__getitem__, ops)
        i = 0
        while i < len(code.co_code):
            op = ord(code.co_code[i])
            i += 1
            if op >= dis.HAVE_ARGUMENT:
                oparg = ord(code.co_code[i]) + ord(code.co_code[i+1])*256
                i += 2
            else:
                oparg = None
            if op in ops:
                name = code.co_names[oparg]
                yield name

    # iterate through sub code objects
    import types
    for subcode in code.co_consts:
        if isinstance(subcode, types.CodeType):
            for g in iterGlobalsUsedInFunc(subcode, fast=fast, loadsOnly=loadsOnly):
                yield g

An updated version might be here.


My use case:

I have some module (songdb) which has some global database objects and I wanted to lazily load them once I called a function which uses the global database variable. I could have manually decorated such functions with a lazy loader or I could automatically detect which functions need it by my iterGlobalsUsedInFunc function.

This is basically the code (full code; was actually extended for classes now), where init automatically decorates such functions:

DBs = {
    "songDb": "songs.db",
    "songHashDb": "songHashs.db",
    "songSearchIndexDb": "songSearchIndex.db",
    }
for db in DBs.keys(): globals()[db] = None

def usedDbsInFunc(f):
    dbs = []
    for name in utils.iterGlobalsUsedInFunc(f, loadsOnly=True):
        if name in DBs:
            dbs += [name]
    return dbs

def init():
    import types
    for fname in globals().keys():
        f = globals()[fname]
        if not isinstance(f, types.FunctionType): continue
        dbs = usedDbsInFunc(f)
        if not dbs: continue
        globals()[fname] = lazyInitDb(*dbs)(f)

def initDb(db):
    if not globals()[db]:
        globals()[db] = DB(DBs[db])

def lazyInitDb(*dbs):
    def decorator(f):
        def decorated(*args, **kwargs):
            for db in dbs:
                initDb(db)
            return f(*args, **kwargs)
        return decorated
    return decorator

Another solution would have been to use an object proxy which lazily loads the database. I have used that elsewhere in this project, thus I have also implemented such object proxy; if you are interested, see here: utils.py:ObjectProxy.

Solution 3:[3]

A dirty way would be to use inspect.getsourcelines() and search for lines containing global <varname>. There are no good methods for this, at least not in inspect module.

Solution 4:[4]

As you already found out, the property f_globals gives you the global namespace in which the function was defined.

From what I can see, the only way to find out which global variables are actually used is to disassemble the function's byte code with dis; look for the byte codes STORE_NAME, STORE_GLOBAL, DELETE_GLOBAL, etc.

Solution 5:[5]

If using Colab / Jupyter

In one cell you run dis redirecting the output to a variable

%%capture dis_output

func_to_check=my_own_function

# dis : Disassembler for Python bytecode
from dis import dis
dis(func_to_check)

And then you can filter its content to detect the use of GLOBALS. Here an example

Version 1 (safer than v2)

# Then grep will find the use of GLOBALS

# Method 1 (kind of safer)
with open('dis_output.txt', 'w') as f:
  f.writelines(dis_output.stdout)

! cat dis_output.txt | grep -i global

Version 2 (not so safe)

# Method 2 (not so safe)
! echo  "{dis_output.stdout}" | grep -i global

Results (example)

14           4 LOAD_GLOBAL              0 (AudioLibrary)
             36 LOAD_GLOBAL              2 (userLanguageAudio)
             50 LOAD_GLOBAL              2 (userLanguageAudio)
             62 LOAD_GLOBAL              3 (LESSON_FILES_DIR)
 27          76 LOAD_GLOBAL              4 (get_ipython)
 30          96 LOAD_GLOBAL              6 (pread)
 31         104 LOAD_GLOBAL              7 (print)
 34     >>  124 LOAD_GLOBAL              7 (print)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rumple Stiltskin
Solution 2
Solution 3 pajton
Solution 4 Aaron Digulla
Solution 5 Community