An identifier such as variable name is "in scope" if it is "bound" (points) to a value.
Identifiers exist in "namespaces". Python namespaces are like dictionaries in that identifiers can be added or removed at execution time. This is called "dynamic scope".
This differs from languages such as C where the scope of an identifier is defined at compile time and is determined by the location of the identifier's declaration in the program. This is called "lexical scope".
a=1
print(a)
del(a)
print(a)
1
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[2], line 4 2 print(a) 3 del(a) ----> 4 print(a) NameError: name 'a' is not defined
locals()
and globals()
functions return dictionaries of variables in the local and global namespaces. Although not recommended, we can manipulate these dictiionaries and add/access/remove variables to show how namespaces are implemented:
locals()['b'+str(3)]=4
print(b3)
globals()['c'+'d']=5
print(cd)
del(locals()['b3'])
print(b3)
4 5
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[3], line 6 4 print(cd) 5 del(locals()['b3']) ----> 6 print(b3) NameError: name 'b3' is not defined
In Python the scope of variables assigned to within a function is limited to that function. Their scope is "local" -- the variables are put in a dictionary which returned by the locals()
function. This dictionary is created on entry to the function and deleted on exit. This allows you to freely re-use names without worrying about conflicts with variables defined in other functions.
Variables that are not assigned to in a function refer to variables in an enclosing function's scope, or in the global (top-level) scope. The global scope can be accessed through a dictionary returned by the globals()
function.
It is possible to assign to a global variable by including it in a global
statement.
It is possible to assign to a variable in the enclosing function's scope by including it in a nonlocal
statement.
In the following example there are three scopes:
x
and y
are bound to an integer with value 1f
where a new variable x
and z
are created in the function's local namespace because they are assigned to, and y
refers to y
in the global namespaces because of the global
statementg
where the variable x
from the function f
scope (not the global x
) is made accessible by a nonlocal
statement. In this example the global variable x
is "shadowed" by the local variable x
.
x=1
y=1
print(x,y)
def f():
global y
x=2
y=2
z=2
print(x,y,z)
def g():
nonlocal x
x=3
print(y)
g()
print(x,y,z)
f()
print(x,y)
print(globals()['x'])
print(z)
1 1 2 2 2 3 2 2 1 2 1
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[4], line 20 18 print(x,y) 19 print(globals()['x']) ---> 20 print(z) NameError: name 'z' is not defined
If an explicit namespace is not given, names are searched for in the following order:
(the acronym is LEGB
).
Python code that is meant to be re-used (a "library") is placed in modules. Modules can be grouped into packages. In many cases:
.py
extension__init__.py
file (which is thus also a module)The statement import
file searches in the list of directories sys.path
for a file named file.py
(a module) or a directory named file containing a file named __init__.py
(a package). The .py
files are executed and any objects created are put in a "module" namespace named file.
__init__.py
is often empty.
Modules and packages can be placed in a package. For example, a import encoders.video
statement would execute encoders/video.py
(a module) or encoders/video/__init__.py
(a package) in the codecs.video
module namespace (assuming encoders/__init__.py
existed).
The above is a simplified description; modules can be found and loaded in other ways.
The example below shows a package named encoders
containing a module named audio
and a package named video
:
encoders\
__init__.py
audio.py
video\
__init__.py
The import statements execute the corresponding .py
files which in this example only add variables to the module namespace. However, modules typically define related types, functions, and constants using class
, def
and assignment statements.
# the directory structure and contents
!dir /s encoders
!type encoders\__init__.py
!type encoders\video\__init__.py
!type encoders\audio.py
import encoders
print(encoders,dir(encoders),sep='\n')
import encoders.video
print(encoders.video,dir(encoders.video),sep='\n')
import encoders.audio
print(encoders.audio,dir(encoders.audio),sep='\n')
print(dir(encoders),sep='\n')
Volume in drive C is Windows Volume Serial Number is 1A85-7D8B Directory of C:\Users\Ed\SharedFolder\bcit\4653\notebooks\lectures\encoders 2023-05-16 07:54 AM <DIR> . 2023-05-16 07:54 AM <DIR> .. 2023-05-15 10:10 PM 4 audio.py 2023-05-16 07:54 AM <DIR> video 2023-05-15 10:10 PM 4 __init__.py 2 File(s) 8 bytes Directory of C:\Users\Ed\SharedFolder\bcit\4653\notebooks\lectures\encoders\video 2023-05-16 07:54 AM <DIR> . 2023-05-16 07:54 AM <DIR> .. 2023-05-15 10:20 PM 4 __init__.py 1 File(s) 4 bytes Total Files Listed: 3 File(s) 12 bytes 5 Dir(s) 293,570,883,584 bytes free a=1 b=1 c=1 <module 'encoders' from 'C:\\Users\\Ed\\SharedFolder\\bcit\\4653\\notebooks\\lectures\\encoders\\__init__.py'> ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'a'] <module 'encoders.video' from 'C:\\Users\\Ed\\SharedFolder\\bcit\\4653\\notebooks\\lectures\\encoders\\video\\__init__.py'> ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'b'] <module 'encoders.audio' from 'C:\\Users\\Ed\\SharedFolder\\bcit\\4653\\notebooks\\lectures\\encoders\\audio.py'> ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'c'] ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'a', 'audio', 'video']
We can also import one or all variables in a module into the global namespace by using the from
file import
name or from
file import *
syntax:
# iimport the cos function into the global namespace:
from math import cos
# import everything into the global namespace (not typically done):
from math import *
#import math
print(globals().keys())
print('math' in globals())
print(pi)
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__builtin__', '__builtins__', '_ih', '_oh', '_dh', 'In', 'Out', 'get_ipython', 'exit', 'quit', 'open', '_', '__', '___', '_i', '_ii', '_iii', '_i1', '_exit_code', 'encoders', '_i2', '_i3', 'cd', '_i4', 'x', 'y', 'f', '_i5', 'newmin', 'min1', 'min2', '_i6', 'cos', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cosh', 'degrees', 'dist', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log1p', 'log10', 'log2', 'modf', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', 'prod', 'perm', 'comb', 'nextafter', 'ulp', 'pi', 'e', 'tau', 'inf', 'nan', '_i7', '_i8', 'math', '_i9']) True 3.141592653589793
Each function has its own local namespace. Each time we create a new function we create a new namespace. This allows functions to store data. Such functions that hold data (state) are called "closures".
In this example the function newmin
is a "factory" function that returns a function object. This function object keeps track of the smallest value it has been called with and returns it.
In Python we typically do this by defining a new type of object using the class
keyword, but closures have less overhead.
# a function that returns a function:
def newmin():
m=None
def f(x):
# 'nonlocal' binds m to the enclosing scope
# instead of the global scope
nonlocal m
if m == None or x<m:
m=x
return m
return f
min1=newmin()
print(min1(15),min1(10),min1(20))
min2=newmin()
print(min2(10),min2(5),min2(15))
print(min1(100),min2(100))
15 10 10 10 5 5 10 5
Each instance of an object also has its own namespace:
class newclass():
def __init__(self,x):
self.x=x
c1=newclass(1)
c2=newclass(2)
x=3
c2.newthing = 4
print(c1.x,c2.x,x,c2.newthing)
print(c1.newthing)
1 2 3 4
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[3], line 11 8 c2.newthing = 4 10 print(c1.x,c2.x,x,c2.newthing) ---> 11 print(c1.newthing) AttributeError: 'newclass' object has no attribute 'newthing'