Namespaces in Python

A namespace in Python refers to the naming system used to ensure that names are unique and won't lead to conflicts. Namespaces map names to objects. Python uses different types of namespaces, including:

LEGB Rule: Scope Resolution in Python

Python resolves variable names using the LEGB rule, which stands for:

Local Scope

A Local scope is the innermost scope. It refers to names defined inside the current function. When Python executes a function, it creates a local namespace specific to that function.

Enclosing Scope

An Enclosing scope occurs when functions are nested. The enclosing function's variables can be accessed by the inner function unless shadowed or overridden.

Global Scope

The Global scope refers to names defined at the top level of a script or module. These names are accessible anywhere in the module unless overridden by a local name.

Built-in Scope

The Built-in scope is the outermost scope that contains names predefined by Python. These are always available, such as print, type, int, len, and more.

Example of LEGB Rule

x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)

    inner()

outer()  # Output: local

If Local Is Not Defined

x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        print(x)
    
    inner()

outer()  # Output: enclosing

nonlocal Keyword

The nonlocal keyword is used to modify a variable in the enclosing (E) scope inside a nested function. It tells Python not to treat the variable as local.

def outer():
    message = "outer"

    def inner():
        nonlocal message
        message = "modified"
        print("Inner:", message)

    inner()
    print("Outer:", message)

outer()

Variable Shadowing

Variable shadowing occurs when a variable in a local scope has the same name as a variable in an outer scope. The inner one hides the outer one.

x = 5

def func():
    x = 10  # Shadows global x
    print(x)

func()       # Output: 10
print(x)     # Output: 5

Using globals() and locals()

Python provides built-in functions to inspect namespaces:

x = 42
print(globals()['x'])  # Access x from global scope

def demo():
    y = 99
    print(locals())     # Shows local variables

demo()

Namespace Collisions and Best Practices

Internal Implementation of Namespaces

Under the hood, Python implements namespaces using dictionaries. You can access them via:

x = 10
print(globals())  # Dictionary of all global symbols

Why Are Namespaces Important?

Example 1: Local Namespace

def greet():
    name = "Alice"
    print("Hello", name)

greet()

Example 2: Global Namespace

language = "Python"

def show():
    print("Programming in", language)

show()

Example 3: Modifying Global Inside Function

count = 10

def update():
    global count
    count += 1

update()
print(count)

Example 4: Built-in Namespace

print(len("hello"))  # 'len' is from built-in namespace

Example 5: Nested Function (Enclosing Namespace)

def outer():
    x = "outer value"

    def inner():
        print(x)  # access enclosing scope

    inner()

outer()

In summary, understanding namespaces, the LEGB rule, and related tools is essential for writing clean and bug-free Python programs.