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
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