Variable Scope

Global, local, built-in, and non-local variables

avatar

Sébastien Boisgrault
Associate Professor, ITN Mines Paris – PSL

Introduction

To know which value a Python identifier refers to at a given point in the code, you need to understand the concept of scope of a variable. There are four distinct scopes in Python.

Global Variables

The function dir, used without arguments, lists the variables defined in the global scope of the interpreter or a Python program.

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

Here we see that 7 variables are predefined in the interpreter and we can display their values:

>>> __annotations__
{}
>>> __builtins__
<module 'builtins' (built-in)>
>>> __doc__
>>> __loader__
<class '_frozen_importlib.BuiltinImporter'>
>>> __name__
'__main__'
>>> __package__
>>> __spec__

By defining a new global variable a, we change the result of dir().

>>> a = 1
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a']

To avoid seeing the predefined values, which are of no interest here, we filter out variable names that start and end with a double underscore.

>>> def is_dunder(name):
...     return name.startswith("__") and name.endswith("__")
... 
>>> [name for name in dir() if not is_dunder(name)]
['a', 'is_dunder']

Note that the newly defined function is_dunder is also listed. It is possible to introduce new variables and then make them disappear using the keyword del.

>>> b = 42
>>> [name for name in dir() if not is_dunder(name)]
['a', 'b', 'is_dunder']
>>> del a
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> [name for name in dir() if not is_dunder(name)]
['b', 'is_dunder']

globals

If you are interested not only in the names of global variables but also in the values they refer to, the function globals provides a dictionary containing both pieces of information.

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'b': 42, 'is_dunder': <function is_dunder at 0x7f29d337b760>}

Again, we can filter out the predefined variables from this structure.

>>> globs = {name: value for name, value in globals().items() if not is_dunder(name)}
>>> globs
{'b': 42, 'is_dunder': <function is_dunder at 0x7f29d337b760>}

Adding or removing variables can be done either directly or by modifying the global variables dictionary.

>>> a = 1
>>> globs["a"]
1
>>> del globs["a"]
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> globs["c"] = 22
>>> c
22

Local Variables

Called in a function, dir() lists the local variables (to the function).

>>> def f():
...     print(dir())
... 
>>> f()
[]
>>> def f():
...     a = 1
...     print(dir())
... 
>>> f()
['a']

In the previous example, a is not defined as a global variable but as a local variable to the function.

>>> def f():
...     a = 1
...     print("a" in globals())
... 
>>> f()
False
>>> def f():
...     a = 1
...     print("a" in locals())
... 
>>> f()
True
>>> def f():
...     a = 1
...     print(locals())
... 
>>> f()
{'a': 1}

Unlike global variables, we cannot modify local variables by modifying the dictionary produced by locals().

>>> def f():
...     a = 1
...     locals()["a"] = 2
...     print(a)
... 
>>> f()
1
Undefined behavior of locals()

To be precise, the consequences of updating the locals() dictionary may depend on the Python interpreter used. For the classic Python interpreter (CPython), these updates have no effect. The help for the locals function is explicit:

locals() returns a dictionary containing the current scope's local variables.
  
NOTE: Whether or not updates to this dictionary will affect name lookups in
the local scope and vice-versa is *implementation dependent* and not
covered by any backwards compatibility guarantees.

Function parameters are part of its local variables.

>>> def f(b):
...     a = 1
...     print(locals())
... 
>>> f(7)
{'b': 7, 'a': 1}

Global variables are accessible for reading from the function code…

>>> c = 2
>>> def f(b):
...     a = 1
...     print(a, b, c)
... 
>>> f(7)
1 7 2

… but not for writing…

>>> def f():
...     c = 3
...     print(c)
... 
>>> f()
3
>>> c
2

… unless they are explicitly declared as global, using the keyword global.

>>> def f():
...     global c
...     c = 3
...     print(c)
... 
>>> c
2
>>> f()
3
>>> c
3

In the presence of “conflict” and without the keyword global, in the code of the function, variables are considered local.

>>> c = 3
>>> def f():
...     c = 2
...     print(locals())
... 
>>> f()
{'c': 2}

In this case, we say that the local variable shadows the global variable.

Built-in Variables

Some variables are predefined, but do not belong to the global scope, but to an even more fundamental scope: the built-in scope.

This is the case, for example, of the function print.

>>> print
<built-in function print>
>>> print(42)
42

You cannot delete a built-in variable as easily as a global variable.

>>> del print
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
>>> print(42)
42

It is technically possible to shadow a built-in variable with a global variable (even if this is probably not a good idea).

>>> print = 9
>>> print(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> print
9

The built-in variable will be accessible again if we get rid of the global variable.

>>> del print
>>> print(42)
42

If you really want to permanently remove a built-in variable (still not a good idea), this is possible by modifying the __dict__ of the builtins module.

>>> import builtins
>>> "print" in builtins.__dict__
True
>>> builtins.__dict__["print"]
<built-in function print>
>>> del builtins.__dict__["print"]
>>> print
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
>>> print(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined

Non-local Variables

It is possible to define a function inside a function. The inner function has access to variables defined in the enclosing function — variables qualified as non-local — for reading only, as well as global variables that are not shadowed by these variables (and as built-in variables that are not shadowed by any other scope). To write to these non-local variables, they must be explicitly declared using the keyword nonlocal.

>>> def f():
...     a = 1
...     def g():
...        nonlocal a
...        a = 3
...     g()
...     print(a)
... 
>>> f()
3