Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python warn me or prevent me from using global variables

Tags:

python

ipython

I've gotten myself in trouble a few times now with accidentially (unintentionally) referencing global variables in a function or method definition.

My question is: is there any way to disallow python from letting me reference a global variable? Or at least warn me that I am referencing a global variable?

x = 123

def myfunc() :
    print x    # throw a warning or something!!!

Let me add that the typical situation where this arrises for my is using IPython as an interactive shell. I use 'execfile' to execute a script that defines a class. In the interpreter, I access the class variable directly to do something useful, then decide I want to add that as a method in my class. When I was in the interpreter, I was referencing the class variable. However, when it becomes a method, it needs to reference 'self'. Here's an example.

class MyClass :

    a = 1
    b = 2

    def add(self) :
        return a+b


m = MyClass()

Now in my interpreter I run the script 'execfile('script.py')', I'm inspecting my class and type: 'm.a * m.b' and decide, that would be a useful method to have. So I modify my code to be, with the non-intentional copy/paste error:

class MyClass :

    a = 1
    b = 2

    def add(self) :
        return a+b


    def mult(self) :
        return m.a * m.b   # I really meant this to be self.a * self.b

This of course still executes in IPython, but it can really confuse me since it is now referencing the previously defined global variable!

Maybe someone has a suggestion given my typical IPython workflow.

like image 464
EpicAdv Avatar asked May 23 '13 18:05

EpicAdv


People also ask

Should you avoid using global variables in Python?

While in many or most other programming languages variables are treated as global if not declared otherwise, Python deals with variables the other way around. They are local, if not otherwise declared. The driving reason behind this approach is that global variables are generally bad practice and should be avoided.

How do you avoid global variables?

The simplest way to avoid globals all together is to simply pass your variables using function arguments. As you can see, the $productData array from the controller (via HTTP request) goes through different layer: The controller receives the HTTP request. The parameters are passed to the model.

How can parameters be used to avoid the use of global variables?

Parameter passing - allows the values of local variables within the main program to be passed to sub-programs without the need to use global variables. The value of these variables (or a copy of the value of these variables) is passed as a parameter to and from sub-programs as necessary.


1 Answers

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.

like image 181
abarnert Avatar answered Sep 20 '22 22:09

abarnert