Python warns me or prevents the use of global variables

Several times I came across problems by accident (inadvertently), referring to global variables in the definition of a function or method.

My question is: is there a way to prevent python from giving a reference to 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 when this is done for me uses IPython as an interactive shell. I use "execfile" to execute a script that defines the class. In the interpreter, I access the class variable directly to do something useful, and then decided that I want to add this as a method in my class. When I was in the interpreter, I referenced a class variable. However, when it becomes a method, it must refer to the self. Here is 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 check my class and type: 'ma * mb' and decide that this would be a useful method to have. Therefore, I am modifying my code to be with an unintended copy / paste error:

 class MyClass : a = 1 b = 2 def add(self) : return a+b def mult(self) : return ma * mb # I really meant this to be self.a * self.b 

This, of course, is still done in IPython, but it can really confuse me as it now refers to a previously defined global variable!

Maybe someone has a suggestion given by my typical IPython workflow.

+6
source share
1 answer

First, you probably don't want to do this. As Martin Peters points out, many things, such as functions and top-level classes, are global.

You can filter this only for non-invoked calls to global variables. Functions, classes, built-in functions, or methods that you import from the C extension module, etc., may be called. This will still not catch cases where, for example, you assign a function to a different name after def . Indeed, everything you come up with will be, at best, very rude guidance, and not what you want to consider as an absolute warning.

Also, no matter how you do it, trying to detect implicit global access but not explicit access (with the global statement) will be very difficult, so hopefully this is not important.


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

However, this is fairly easy to do with reflection inside the interpreter.

The documentation for the inspect module has a nice schedule that shows you standard elements of various types.

This function will provide you with a list of all global names referenced by the associated method, unbound method, function or code object:

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

If you want to handle non-calls, 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 is not ideal (for example, if the globals function has a custom built-in replacement, we will not look for it correctly), but it is probably good enough.


A simple example of its use:

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

And you can easily compose the import module and recursively lay out your callables and call get_globals() for each of them, which will work for the main cases (top-level functions and methods of top-level and nested classes), although it will not work for anything, dynamically defined (for example, functions or classes defined inside functions).


If you only care about CPython, another option is to use the dis module to scan the entire bytecode in the or module. pyc file (or class or something else) and write down each LOAD_GLOBAL op.

One of the main advantages of this inspect method is that it will find functions that have been compiled, even if they have not been created yet.

The disadvantage is that there is no way to search for names (how could it be if some of them are not already created?), So you cannot easily filter the calling files. You can try to do something fantastic, for example, connect LOAD_GLOBAL ops to the corresponding CALL_FUNCTION (and related) operations, but ... it starts to get complicated.


Finally, if you want to dynamically connect things, you can always replace globals with a shell that warns every time you access it. For instance:

 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 easily filter non-contact files:

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

You can also easily throw an exception instead of a warning.

You can associate this with your code in various ways. The most obvious is import capture, which gives each new GlobalsWrapper module around its normally built globals . Although I'm not sure how this will interact with the C extension modules, I assume that it will either work, be ignored harmlessly, or it is probably fine. The only problem is that this will not affect your top level script. If this is important, you can write a shell script that execfile main script with GlobalsWrapper , or something like that.

+3
source

Source: https://habr.com/ru/post/945716/


All Articles