Python warn me or prevent me from using global variables

First, you probably don't want to do this. As Martijn Pieters points out, many things, like top-level functions and classes, are globals.

You could filter this for only non-callable globals. Functions, classes, builtin-function-or-methods that you import from a C extension module, etc. are callable. You might also want to filter out modules (anything you import is a global). That still won't catch cases where you, say, assign a function to another name after the def. You could add some kind of whitelisting for that (which would also allow you to create global "constants" that you can use without warnings). Really, anything you come up with will be a very rough guide at best, not something you want to treat as an absolute warning.

Also, no matter how you do it, trying to detect implicit global access, but not explicit access (with a global statement) is going to be very hard, so hopefully that isn't important.


There is no obvious way to detect all implicit uses of global variables at the source level.

However, it's pretty easy to do with reflection from inside the interpreter.

The documentation for the inspect module has a nice chart that shows you the standard members of various types. Note that some of them have different names in Python 2.x and Python 3.x.

This function will get you a list of all the global names accessed by a bound method, unbound method, function, or code object in both versions:

def get_globals(thing):
    thing = getattr(thing, 'im_func', thing)
    thing = getattr(thing, '__func__', thing)
    thing = getattr(thing, 'func_code', thing)
    thing = getattr(thing, '__code__', thing)
    return thing.co_names

If you want to only handle non-callables, you can filter it:

def get_callable_globals(thing):
    thing = getattr(thing, 'im_func', thing)
    func_globals = getattr(thing, 'func_globals', {})
    thing = getattr(thing, 'func_code', thing)
    return [name for name in thing.co_names
            if callable(func_globals.get(name))]

This isn't perfect (e.g., if a function's globals have a custom builtins replacement, we won't look it up properly), but it's probably good enough.


A simple example of using it:

>>> def foo(myparam):
...     myglobal
...     mylocal = 1
>>> print get_globals(foo)
('myglobal',)

And you can pretty easily import a module and recursively walk its callables and call get_globals() on each one, which will work for the major cases (top-level functions, and methods of top-level and nested classes), although it won't work for anything defined dynamically (e.g., functions or classes defined inside functions).


If you only care about CPython, another option is to use the dis module to scan all the bytecode in a module, or .pyc file (or class, or whatever), and log each LOAD_GLOBAL op.

One major advantage of this over the inspect method is that it will find functions that have been compiled, even if they haven't been created yet.

The disadvantage is that there is no way to look up the names (how could there be, if some of them haven't even been created yet?), so you can't easily filter out callables. You can try to do something fancy, like connecting up LOAD_GLOBAL ops to corresponding CALL_FUNCTION (and related) ops, but… that's starting to get pretty complicated.


Finally, if you want to hook things dynamically, you can always replace globals with a wrapper that warns every time you access it. For example:

class GlobalsWrapper(collections.MutableMapping):
    def __init__(self, globaldict):
        self.globaldict = globaldict
    # ... implement at least __setitem__, __delitem__, __iter__, __len__
    # in the obvious way, by delegating to self.globaldict
    def __getitem__(self, key):
        print >>sys.stderr, 'Warning: accessing global "{}"'.format(key)
        return self.globaldict[key]

globals_wrapper = GlobalsWrapper(globals())

Again, you can filter on non-callables pretty easily:

    def __getitem__(self, key):
        value = self.globaldict[key]
        if not callable(value):
            print >>sys.stderr, 'Warning: accessing global "{}"'.format(key)
        return value

Obviously for Python 3 you'd need to change the print statement to a print function call.

You can also raise an exception instead of warning pretty easily. Or you might want to consider using the warnings module.

You can hook this into your code in various different ways. The most obvious one is an import hook that gives each new module a GlobalsWrapper around its normally-built globals. Although I'm not sure how that will interact with C extension modules, but my guess is that it will either work, or be harmlessly ignored, either of which is probably fine. The only problem is that this won't affect your top-level script. If that's important, you can write a wrapper script that execfiles the main script with a GlobalsWrapper, or something like that.

Tags:

Python

Ipython