Namespace
1. Definition
A namespace is a maping from names to objects. Most namespaces are currently implemented as Python dictionaries.
2. Three Types of Namespaces
- Built-in names: Names predefined in Python, such as function names
abs,char, and exception namesBaseException,Exception, etc. - Global names: Names defined in a module, recording the module's variables, encluding functions, classes, imported modules, module-level variables and constants.
- Local names: Names defined inside a function, recording the function's variables, including parameters and locally defined variables. (Also includes names defined in a class)

3. Namespace Lookup Order
Suppose we want to use the variable python. Python's lookup order is: local namespace → global namespace → built-in namespace.
If the variable python is not found, it will raise a NameError exception: NameError: name 'python' is not defined.
4. Examples
total = 0 # global variable
def sum(arg1, arg2):
total = arg1 + arg2 # local variable
return total
print(sum(1, 2)) # output: 3
Modifying a global variable using global:
num = 1
def fun1():
global num
print(num)
num = 2311
print(num)
fun1()
# Output:
# 1
# 2311
Modifying a variable in the enclosing scope (closure) using nonlocal:
def outer():
num = 10
def inner():
nonlocal num
print(num)
num = 100
print(num)
inner()
outer()
# Output:
# 10
# 100
Closures
Definition of a Closure
A closure occurs when a nested function references a variable from its enclosing function, and the enclosing function returns the nested function. The nested function retains access to that variable even after the outer function has finished executing.
Example:
def outer(a):
b = 10
def inner():
print(a + b)
return inner # returns the function reference, not calling it
closure_func = outer(5)
closure_func() # output: 15
Modifying a closure variable using nonlocal:
def outer(a):
b = 10
def inner():
nonlocal b
b += 1
print(a + b)
return inner
closure_func = outer(5)
closure_func() # output: 16