You assign x = 10 inside a function. Outside, x is undefined. Python follows the LEGB rule to find variables: Local, Enclosing, Global, Built-in. Understanding scope prevents confusing bugs.

Local vs global variables

Understand where variables are visible.

local_global.py
Replay: real traced execution (multi-file project)
def main():
    print("=== Local vs Global Variables ===\n")

    global_demo()

    print("\n=== Local Variables ===")
    local_demo()

    print("\n=== Checking Global After Function ===")
    print(f"message is still: '{message}'")

    print("\n=== Shadowing ===")
    shadow_demo()
    print(f"Global message unchanged: '{message}'")

# Global variable - defined at module level
message = "Hello from global scope"

def global_demo():
    # Can READ global without keyword
    print("Inside function, reading global:")
    print(f"  message = '{message}'")

def local_demo():
    # This creates a LOCAL variable, doesn't affect global
    greeting = "Hello from local scope"
    count = 42
    print(f"  greeting = '{greeting}'")
    print(f"  count = {count}")
    # greeting and count die when function returns

def shadow_demo():
    # This 'message' shadows the global one
    message = "I'm a local variable!"
    print(f"  Local message: '{message}'")
    # Global 'message' is unchanged

if __name__ == "__main__":
    main()
  1. message ← Hello from global scope

    19# Global variable - defined at module level  #?globalvar20message→ Hello from global scope = "Hello from global scope"
  2. def main():

    1#@var=default,shadow2def main():3    print("=== Local vs Global Variables ===\n")4    5    global_demo()  #?global
    output=== Local vs Global Variables ===
  3. def global_demo(): # Can READ global without keyword

    5    global_demo()  #?global6    7    print("\n=== Local Variables ===")8    local_demo()9    10    print("\n=== Checking Global After Function ===")11    print(f"message is still: '{message}'")  #?persist12    13    #@var=_,!14    print("\n=== Shadowing ===")15    shadow_demo()16    print(f"Global message unchanged: '{message}'")17    #@var=_,!1819# Global variable - defined at module level  #?globalvar20message = "Hello from global scope"2122def global_demo():23    # Can READ global without keyword24    print("Inside function, reading global:")25    print(f"  message = '{messageHello from global scope}'")
    outputInside function, reading global:
      message = 'Hello from global scope'
    
    === Local Variables ===
  4. greeting ← Hello from local scope, count ← 42

    7    print("\n=== Local Variables ===")8    local_demo()9    10    print("\n=== Checking Global After Function ===")11    print(f"message is still: '{messageHello from global scope}'")  #?persist12    13    #@var=_,!14    print("\n=== Shadowing ===")15    shadow_demo()16    print(f"Global message unchanged: '{message}'")17    #@var=_,!1819# Global variable - defined at module level  #?globalvar20message = "Hello from global scope"2122def global_demo():23    # Can READ global without keyword24    print("Inside function, reading global:")25    print(f"  message = '{message}'")2627def local_demo():  #?localvar28    # This creates a LOCAL variable, doesn't affect global29    greeting→ Hello from local scope = "Hello from local scope"30    count→ 42 = 4231    print(f"  greeting = '{greetingHello from local scope}'")32    print(f"  count = {count42}")33    # greeting and count die when function returns
    output  greeting = 'Hello from local scope'
      count = 42
    
    === Checking Global After Function ===
    message is still: 'Hello from global scope'
    
    === Shadowing ===
  5. message ← I'm a local variable!

    14    print("\n=== Shadowing ===")15    shadow_demo()16    print(f"Global message unchanged: '{messageHello from global scope}'")17    #@var=_,!1819# Global variable - defined at module level  #?globalvar20message = "Hello from global scope"2122def global_demo():23    # Can READ global without keyword24    print("Inside function, reading global:")25    print(f"  message = '{message}'")2627def local_demo():  #?localvar28    # This creates a LOCAL variable, doesn't affect global29    greeting = "Hello from local scope"30    count = 4231    print(f"  greeting = '{greeting}'")32    print(f"  count = {count}")33    # greeting and count die when function returns3435#@var=_,!36def shadow_demo():37    # This 'message' shadows the global one38    message→ I'm a local variable! = "I'm a local variable!"  #?shadowvar39    print(f"  Local message: '{messageI'm a local variable!}'")40    # Global 'message' is unchanged
    output  Local message: 'I'm a local variable!'
    Global message unchanged: 'Hello from global scope'
  6. main()

    43if __name__ == "__main__":44    main()45#@help global

Variables assigned inside a function are local by default.

local variable Variable created inside function. Only visible within that function.

The LEGB rule

Python's variable lookup order.

legb_rule.py
Replay: real traced execution (multi-file project)
def main():
    print("=== LEGB Rule ===\n")
    print("Python searches names in order:")
    print("L → Local (current function)")
    print("E → Enclosing (outer function)")
    print("G → Global (module level)")
    print("B → Built-in (Python's names)")

    print("\n" + "="*40)
    demonstrate_legb()

    print("\n=== Built-in Scope ===")
    demonstrate_builtin()

x = "global x"  # Global scope

def demonstrate_legb():
    print("\n--- LEGB Demonstration ---\n")

    x = "enclosing x"  # Enclosing scope

    def inner():
        x = "local x"  # Local scope
        print(f"In inner(): x = '{x}'")  # Finds local first

    def inner_no_local():
        # No local x here
        print(f"In inner_no_local(): x = '{x}'")  # Finds enclosing

    inner()
    inner_no_local()
    print(f"In outer(): x = '{x}'")  # Uses enclosing

def demonstrate_builtin():
    # len is a built-in function
    numbers = [1, 2, 3, 4, 5]
    print(f"len({numbers}) = {len(numbers)}")

    # We could shadow it (don't do this!)
    # len = 10  # This would break len()!

if __name__ == "__main__":
    main()
  1. x ← global x

    15x→ global x = "global x"  # Global scope  #?globalx
  2. def main():

    1def main():2    print("=== LEGB Rule ===\n")3    print("Python searches names in order:")4    print("L → Local (current function)")5    print("E → Enclosing (outer function)")6    print("G → Global (module level)")7    print("B → Built-in (Python's names)")8    9    print("\n" + "="*40)10    demonstrate_legb()
    output=== LEGB Rule ===
    Python searches names in order:
    L → Local (current function)
    E → Enclosing (outer function)
    G → Global (module level)
    B → Built-in (Python's names)
    
    ========================================
  3. x ← enclosing x

    17def demonstrate_legb():18    print("\n--- LEGB Demonstration ---\n")19    20    x→ enclosing x = "enclosing x"  # Enclosing scope  #?enclosing21    22    def inner():23        x = "local x"  # Local scope  #?local24        print(f"In inner(): x = '{x}'")  # Finds local first25    26    def inner_no_local():  #?nolocalx27        # No local x here28        print(f"In inner_no_local(): x = '{x}'")  # Finds enclosing29    30    inner()31    inner_no_local()
    output
    --- LEGB Demonstration ---
  4. x ← local x

    22def inner():23    x→ local x = "local x"  # Local scope  #?local24    print(f"In inner(): x = '{xlocal x}'")  # Finds local first
    outputIn inner(): x = 'local x'
  5. inner()

    30inner()31inner_no_local()32print(f"In outer(): x = '{x}'")  # Uses enclosing
  6. def inner_no_local(): #?nolocalx # No local x here

    26def inner_no_local():  #?nolocalx27    # No local x here28    print(f"In inner_no_local(): x = '{xenclosing x}'")  # Finds enclosing
    outputIn inner_no_local(): x = 'enclosing x'
  7. print(f"In outer(): x = '{x}'") # Uses enclosing

    9    print("\n" + "="*40)10    demonstrate_legb()11    12    print("\n=== Built-in Scope ===")  #?builtin13    demonstrate_builtin()1415x = "global x"  # Global scope  #?globalx1617def demonstrate_legb():18    print("\n--- LEGB Demonstration ---\n")19    20    x = "enclosing x"  # Enclosing scope  #?enclosing21    22    def inner():23        x = "local x"  # Local scope  #?local24        print(f"In inner(): x = '{x}'")  # Finds local first25    26    def inner_no_local():  #?nolocalx27        # No local x here28        print(f"In inner_no_local(): x = '{x}'")  # Finds enclosing29    30    inner()31    inner_no_local()32    print(f"In outer(): x = '{xenclosing x}'")  # Uses enclosing
    outputIn outer(): x = 'enclosing x'
    
    === Built-in Scope ===
  8. numbers ← [1, 2, 3, 4, 5]

    12    print("\n=== Built-in Scope ===")  #?builtin13    demonstrate_builtin()1415x = "global x"  # Global scope  #?globalx1617def demonstrate_legb():18    print("\n--- LEGB Demonstration ---\n")19    20    x = "enclosing x"  # Enclosing scope  #?enclosing21    22    def inner():23        x = "local x"  # Local scope  #?local24        print(f"In inner(): x = '{x}'")  # Finds local first25    26    def inner_no_local():  #?nolocalx27        # No local x here28        print(f"In inner_no_local(): x = '{x}'")  # Finds enclosing29    30    inner()31    inner_no_local()32    print(f"In outer(): x = '{x}'")  # Uses enclosing3334def demonstrate_builtin():35    # len is a built-in function  #?builtinuse36    numbers→ [1, 2, 3, 4, 5] = [1, 2, 3, 4, 5]37    print(f"len({numbers[1, 2, 3, 4, 5]}) = {len(numbers)}")
    outputlen([1, 2, 3, 4, 5]) = 5
  9. main()

    42if __name__ == "__main__":43    main()44#@help globalx

Local → Enclosing → Global → Built-in. First match wins.

LEGB Name resolution order: Local, Enclosing, Global, Built-in.

The global keyword

Modify a global variable from inside a function.

global_keyword.py
Replay: real traced execution (multi-file project)
def main():
    print("=== The 'global' Keyword ===\n")

    print(f"Initial counter: {counter}")

    # This works - reading global
    show_counter()

    # This modifies global
    increment_counter()
    increment_counter()
    increment_counter()

    print(f"\nFinal counter: {counter}")

    print("\n=== Without vs With 'global' ===")
    compare_approaches()

    print("\n=== Why Global is Often Bad ===")
    demonstrate_problem()

counter = 0  # Global variable

def show_counter():
    # Reading global - no keyword needed
    print(f"Counter is: {counter}")

def increment_counter():
    global counter  # Required to MODIFY global
    old = counter
    counter += 1
    print(f"Incremented: {old} → {counter}")

def compare_approaches():
    # Without global - creates local!
    def broken_increment():
        # counter = counter + 1  # Error! Or creates local
        local_counter = 0
        local_counter += 1
        print(f"Local only: {local_counter}")

    broken_increment()
    print(f"Global unchanged: {counter}")

total = 0

def add_to_total(value):
    global total
    total += value

def demonstrate_problem():
    global total
    total = 0

    add_to_total(10)
    add_to_total(20)
    print(f"Total: {total}")

    print("\nProblem: Hard to track who modifies 'total'")
    print("Better: Return values instead of modifying globals")

if __name__ == "__main__":
    main()
  1. counter ← 0, total ← 0

    25counter→ 0 = 0  # Global variable  #?globalcounter2627def show_counter():28    # Reading global - no keyword needed29    print(f"Counter is: {counter}")3031def increment_counter():  #?globalkey32    global counter  # Required to MODIFY global33    old = counter34    counter += 135    print(f"Incremented: {old} → {counter}")3637def compare_approaches():38    # Without global - creates local!  #?withoutkw39    def broken_increment():40        # counter = counter + 1  # Error! Or creates local41        local_counter = 042        local_counter += 143        print(f"Local only: {local_counter}")44    45    broken_increment()46    print(f"Global unchanged: {counter}")4748#@var=_,!49total→ 0 = 0
  2. def main():

    1#@var=default,badPractice2def main():3    print("=== The 'global' Keyword ===\n")4    5    print(f"Initial counter: {counter0}")6    7    # This works - reading global  #?read8    show_counter()
    output=== The 'global' Keyword ===
    Initial counter: 0
  3. def show_counter(): # Reading global - no keyword needed

    7    # This works - reading global  #?read8    show_counter()9    10    # This modifies global  #?modify11    increment_counter()12    increment_counter()13    increment_counter()14    15    print(f"\nFinal counter: {counter}")16    17    print("\n=== Without vs With 'global' ===")18    compare_approaches()19    20    #@var=_,!21    print("\n=== Why Global is Often Bad ===")22    demonstrate_problem()23    #@var=_,!2425counter = 0  # Global variable  #?globalcounter2627def show_counter():28    # Reading global - no keyword needed29    print(f"Counter is: {counter0}")
    outputCounter is: 0
  4. old ← 0, counter ← 1

    pass 1 of 3
    10    # This modifies global  #?modify11    increment_counter()12    increment_counter()13    increment_counter()14    15    print(f"\nFinal counter: {counter}")16    17    print("\n=== Without vs With 'global' ===")18    compare_approaches()19    20    #@var=_,!21    print("\n=== Why Global is Often Bad ===")22    demonstrate_problem()23    #@var=_,!2425counter = 0  # Global variable  #?globalcounter2627def show_counter():28    # Reading global - no keyword needed29    print(f"Counter is: {counter}")3031def increment_counter():  #?globalkey32    global counter  # Required to MODIFY global33    old→ 0 = counter034    counter→ 1 += 135    print(f"Incremented: {old0} → {counter1}")
    outputIncremented: 0 → 1
    All 3 passes — pass 1 is the card above
    passoldcounterlocal_counter
    100 1
    211 2
    322 31 0
  5. local_counter ← 0

    38# Without global - creates local!  #?withoutkw39def broken_increment():40    # counter = counter + 1  # Error! Or creates local41    local_counter→ 0 = 042    local_counter→ 1 += 143    print(f"Local only: {local_counter1}")
    outputLocal only: 1
  6. print(f"Global unchanged: {counter}")

    17    print("\n=== Without vs With 'global' ===")18    compare_approaches()19    20    #@var=_,!21    print("\n=== Why Global is Often Bad ===")22    demonstrate_problem()23    #@var=_,!2425counter = 0  # Global variable  #?globalcounter2627def show_counter():28    # Reading global - no keyword needed29    print(f"Counter is: {counter}")3031def increment_counter():  #?globalkey32    global counter  # Required to MODIFY global33    old = counter34    counter += 135    print(f"Incremented: {old} → {counter}")3637def compare_approaches():38    # Without global - creates local!  #?withoutkw39    def broken_increment():40        # counter = counter + 1  # Error! Or creates local41        local_counter = 042        local_counter += 143        print(f"Local only: {local_counter}")44    45    broken_increment()46    print(f"Global unchanged: {counter3}")
    outputGlobal unchanged: 3
    
    === Why Global is Often Bad ===
  7. total ← 0

    55def demonstrate_problem():56    global total57    total→ 0 = 058    59    add_to_total(10)60    add_to_total(20)
  8. total ← 10

    pass 1 of 2
    51def add_to_total(value10):  #?badpractice52    global total53    total→ 10 += value10
  9. add_to_total(10)

    59add_to_total(10)60add_to_total(20)61print(f"Total: {total}")
  10. total ← 30

    pass 2 of 2
    51def add_to_total(value20):  #?badpractice52    global total53    total→ 30 += value20
  11. print(f"Total: {total}")

    21    print("\n=== Why Global is Often Bad ===")22    demonstrate_problem()23    #@var=_,!2425counter = 0  # Global variable  #?globalcounter2627def show_counter():28    # Reading global - no keyword needed29    print(f"Counter is: {counter}")3031def increment_counter():  #?globalkey32    global counter  # Required to MODIFY global33    old = counter34    counter += 135    print(f"Incremented: {old} → {counter}")3637def compare_approaches():38    # Without global - creates local!  #?withoutkw39    def broken_increment():40        # counter = counter + 1  # Error! Or creates local41        local_counter = 042        local_counter += 143        print(f"Local only: {local_counter}")44    45    broken_increment()46    print(f"Global unchanged: {counter}")4748#@var=_,!49total = 05051def add_to_total(value):  #?badpractice52    global total53    total += value5455def demonstrate_problem():56    global total57    total = 058    59    add_to_total(10)60    add_to_total(20)61    print(f"Total: {total30}")62    63    print("\nProblem: Hard to track who modifies 'total'")64    print("Better: Return values instead of modifying globals")65#@var=_,!
    outputTotal: 30
    
    Problem: Hard to track who modifies 'total'
    Better: Return values instead of modifying globals
  12. main()

    67if __name__ == "__main__":68    main()69#@help globalcounter

Without global, assignment creates a local variable instead.

global Declare `global x` to modify module-level variable from inside a function.

The nonlocal keyword

Modify an enclosing function's variable.

nonlocal_keyword.py
Replay: real traced execution (multi-file project)
def main():
    print("=== The 'nonlocal' Keyword ===\n")

    counter_example()

    print("\n=== Accumulator Example ===")
    accumulator_example()

    print("\n=== Without nonlocal ===")
    without_nonlocal()

def counter_example():
    print("--- Counter with Closure ---")

    count = 0  # Enclosing scope variable

    def increment():
        nonlocal count  # Modify enclosing, not create local
        count += 1
        print(f"Count: {count}")

    def decrement():
        nonlocal count
        count -= 1
        print(f"Count: {count}")

    increment()
    increment()
    increment()
    decrement()

    print(f"Final count: {count}")

def accumulator_example():
    print("--- Running Total ---")

    total = 0

    def add(value):
        nonlocal total
        total += value
        return total

    print(f"Add 10: {add(10)}")
    print(f"Add 5: {add(5)}")
    print(f"Add 3: {add(3)}")
    print(f"Add 7: {add(7)}")

def without_nonlocal():
    print("--- Without nonlocal (fails) ---")

    message = "original"

    def try_modify():
        # Without nonlocal, this creates a LOCAL variable
        message = "modified"  # New local, doesn't touch outer!
        print(f"Inside: {message}")

    try_modify()
    print(f"Outside: {message}")  # Still "original"!

if __name__ == "__main__":
    main()
  1. def main():

    1def main():2    print("=== The 'nonlocal' Keyword ===\n")3    4    counter_example()
    output=== The 'nonlocal' Keyword ===
  2. count ← 0

    12def counter_example():13    print("--- Counter with Closure ---")14    15    count→ 0 = 0  # Enclosing scope variable  #?enclosing16    17    def increment():  #?nonlocalkw18        nonlocal count  # Modify enclosing, not create local19        count += 120        print(f"Count: {count}")21    22    def decrement():23        nonlocal count24        count -= 125        print(f"Count: {count}")26    27    increment()28    increment()
    output--- Counter with Closure ---
  3. count ← 1

    pass 1 of 3
    17def increment():  #?nonlocalkw18    nonlocal count  # Modify enclosing, not create local19    count→ 1 += 120    print(f"Count: {count1}")
    outputCount: 1
    All 3 passes — pass 1 is the card above
    passcount
    10 1
    21 2
    32 3
  4. increment()

    27increment()28increment()29increment()
  5. increment()

    27increment()28increment()29increment()30decrement()
  6. increment()

    28increment()29increment()30decrement()
  7. count ← 2

    22def decrement():23    nonlocal count24    count→ 2 -= 125    print(f"Count: {count2}")
    outputCount: 2
  8. print(f"Final count: {count}")

    4    counter_example()5    6    print("\n=== Accumulator Example ===")7    accumulator_example()8    9    print("\n=== Without nonlocal ===")10    without_nonlocal()1112def counter_example():13    print("--- Counter with Closure ---")14    15    count = 0  # Enclosing scope variable  #?enclosing16    17    def increment():  #?nonlocalkw18        nonlocal count  # Modify enclosing, not create local19        count += 120        print(f"Count: {count}")21    22    def decrement():23        nonlocal count24        count -= 125        print(f"Count: {count}")26    27    increment()28    increment()29    increment()30    decrement()31    32    print(f"Final count: {count2}")
    outputFinal count: 2
    
    === Accumulator Example ===
  9. total ← 0

    34def accumulator_example():  #?accumulator35    print("--- Running Total ---")36    37    total→ 0 = 038    39    def add(value):40        nonlocal total41        total += value42        return total43    44    print(f"Add 10: {add(10)}")45    print(f"Add 5: {add(5)}")
    output--- Running Total ---
  10. total ← 10

    pass 1 of 4
    39def add(value10):40    nonlocal total41    total→ 10 += value1042    return total10
    All 4 passes — pass 1 is the card above
    passvaluetotal
    1100 10
    2510 15
    3315 18
    4718 25
  11. print(f"Add 10: {add(10)}")

    44print(f"Add 10: {add(10)}")45print(f"Add 5: {add(5)}")46print(f"Add 3: {add(3)}")
    outputAdd 10: 10
  12. print(f"Add 5: {add(5)}")

    44print(f"Add 10: {add(10)}")45print(f"Add 5: {add(5)}")46print(f"Add 3: {add(3)}")47print(f"Add 7: {add(7)}")
    outputAdd 5: 15
  13. print(f"Add 3: {add(3)}")

    45print(f"Add 5: {add(5)}")46print(f"Add 3: {add(3)}")47print(f"Add 7: {add(7)}")
    outputAdd 3: 18
  14. print(f"Add 7: {add(7)}")

    6    print("\n=== Accumulator Example ===")7    accumulator_example()8    9    print("\n=== Without nonlocal ===")10    without_nonlocal()1112def counter_example():13    print("--- Counter with Closure ---")14    15    count = 0  # Enclosing scope variable  #?enclosing16    17    def increment():  #?nonlocalkw18        nonlocal count  # Modify enclosing, not create local19        count += 120        print(f"Count: {count}")21    22    def decrement():23        nonlocal count24        count -= 125        print(f"Count: {count}")26    27    increment()28    increment()29    increment()30    decrement()31    32    print(f"Final count: {count}")3334def accumulator_example():  #?accumulator35    print("--- Running Total ---")36    37    total = 038    39    def add(value):40        nonlocal total41        total += value42        return total43    44    print(f"Add 10: {add(10)}")45    print(f"Add 5: {add(5)}")46    print(f"Add 3: {add(3)}")47    print(f"Add 7: {add(7)}")
    outputAdd 7: 25
    
    === Without nonlocal ===
  15. message ← original

    49def without_nonlocal():  #?without50    print("--- Without nonlocal (fails) ---")51    52    message→ original = "original"53    54    def try_modify():55        # Without nonlocal, this creates a LOCAL variable56        message = "modified"  # New local, doesn't touch outer!57        print(f"Inside: {message}")58    59    try_modify()60    print(f"Outside: {message}")  # Still "original"!
    output--- Without nonlocal (fails) ---
  16. message ← modified

    54def try_modify():55    # Without nonlocal, this creates a LOCAL variable56    message→ modified = "modified"  # New local, doesn't touch outer!57    print(f"Inside: {messagemodified}")
    outputInside: modified
  17. print(f"Outside: {message}") # Still "original"!

    9    print("\n=== Without nonlocal ===")10    without_nonlocal()1112def counter_example():13    print("--- Counter with Closure ---")14    15    count = 0  # Enclosing scope variable  #?enclosing16    17    def increment():  #?nonlocalkw18        nonlocal count  # Modify enclosing, not create local19        count += 120        print(f"Count: {count}")21    22    def decrement():23        nonlocal count24        count -= 125        print(f"Count: {count}")26    27    increment()28    increment()29    increment()30    decrement()31    32    print(f"Final count: {count}")3334def accumulator_example():  #?accumulator35    print("--- Running Total ---")36    37    total = 038    39    def add(value):40        nonlocal total41        total += value42        return total43    44    print(f"Add 10: {add(10)}")45    print(f"Add 5: {add(5)}")46    print(f"Add 3: {add(3)}")47    print(f"Add 7: {add(7)}")4849def without_nonlocal():  #?without50    print("--- Without nonlocal (fails) ---")51    52    message = "original"53    54    def try_modify():55        # Without nonlocal, this creates a LOCAL variable56        message = "modified"  # New local, doesn't touch outer!57        print(f"Inside: {message}")58    59    try_modify()60    print(f"Outside: {messageoriginal}")  # Still "original"!
    outputOutside: original
  18. main()

    62if __name__ == "__main__":63    main()64#@help enclosing

nonlocal reaches into the enclosing function's scope.

nonlocal Declare `nonlocal x` to modify variable from enclosing (not global) scope.

Closures remember scope

Functions can capture variables from their enclosing scope.

example
closure.py
Replay: real traced execution (multi-file project)
def main():
    print("=== Closures ===\n")
    print("A closure 'remembers' variables from enclosing scope")
    print("even after the outer function has returned.\n")

    # Create closures
    counter_a = make_counter()
    counter_b = make_counter()

    print("Two independent counters:")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_b(): {counter_b()}")  # Independent!
    print(f"counter_b(): {counter_b()}")

    print("\n=== Multiplier Factory ===")
    double_factor = 2
    triple_factor = 3
    double = make_multiplier(double_factor)
    triple = make_multiplier(triple_factor)

    print(f"times {double_factor}(5) = {double(5)}")
    print(f"times {triple_factor}(5) = {triple(5)}")
    print(f"times {double_factor}(10) = {double(10)}")

    print("\n=== Practical: Logger ===")
    info_log = make_logger("INFO")
    error_log = make_logger("ERROR")

    info_log("Application started")
    info_log("Loading config")
    error_log("Connection failed!")

def make_counter():
    count = 0  # Captured by inner function

    def counter():
        nonlocal count
        count += 1
        return count

    return counter  # Return the inner function

def make_multiplier(factor):
    # 'factor' is captured by inner function
    def multiply(x):
        return x * factor  # Uses captured factor

    return multiply

def make_logger(level):
    """Factory for creating loggers with preset level."""
    def log(message):
        print(f"[{level}] {message}")
    return log

if __name__ == "__main__":
    main()
def main():
    print("=== Closures ===\n")
    print("A closure 'remembers' variables from enclosing scope")
    print("even after the outer function has returned.\n")

    # Create closures
    counter_a = make_counter()
    counter_b = make_counter()

    print("Two independent counters:")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_b(): {counter_b()}")  # Independent!
    print(f"counter_b(): {counter_b()}")

    print("\n=== Multiplier Factory ===")
    double_factor = 4
    triple_factor = 3
    double = make_multiplier(double_factor)
    triple = make_multiplier(triple_factor)

    print(f"times {double_factor}(5) = {double(5)}")
    print(f"times {triple_factor}(5) = {triple(5)}")
    print(f"times {double_factor}(10) = {double(10)}")

    print("\n=== Practical: Logger ===")
    info_log = make_logger("INFO")
    error_log = make_logger("ERROR")

    info_log("Application started")
    info_log("Loading config")
    error_log("Connection failed!")

def make_counter():
    count = 0  # Captured by inner function

    def counter():
        nonlocal count
        count += 1
        return count

    return counter  # Return the inner function

def make_multiplier(factor):
    # 'factor' is captured by inner function
    def multiply(x):
        return x * factor  # Uses captured factor

    return multiply

def make_logger(level):
    """Factory for creating loggers with preset level."""
    def log(message):
        print(f"[{level}] {message}")
    return log

if __name__ == "__main__":
    main()
def main():
    print("=== Closures ===\n")
    print("A closure 'remembers' variables from enclosing scope")
    print("even after the outer function has returned.\n")

    # Create closures
    counter_a = make_counter()
    counter_b = make_counter()

    print("Two independent counters:")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_a(): {counter_a()}")
    print(f"counter_b(): {counter_b()}")  # Independent!
    print(f"counter_b(): {counter_b()}")

    print("\n=== Multiplier Factory ===")
    double_factor = 2
    triple_factor = 5
    double = make_multiplier(double_factor)
    triple = make_multiplier(triple_factor)

    print(f"times {double_factor}(5) = {double(5)}")
    print(f"times {triple_factor}(5) = {triple(5)}")
    print(f"times {double_factor}(10) = {double(10)}")

    print("\n=== Practical: Logger ===")
    info_log = make_logger("INFO")
    error_log = make_logger("ERROR")

    info_log("Application started")
    info_log("Loading config")
    error_log("Connection failed!")

def make_counter():
    count = 0  # Captured by inner function

    def counter():
        nonlocal count
        count += 1
        return count

    return counter  # Return the inner function

def make_multiplier(factor):
    # 'factor' is captured by inner function
    def multiply(x):
        return x * factor  # Uses captured factor

    return multiply

def make_logger(level):
    """Factory for creating loggers with preset level."""
    def log(message):
        print(f"[{level}] {message}")
    return log

if __name__ == "__main__":
    main()
  1. def main():

    1#@var=default,practical2def main():3    print("=== Closures ===\n")4    print("A closure 'remembers' variables from enclosing scope")5    print("even after the outer function has returned.\n")6    7    # Create closures  #?create8    counter_a = make_counter()9    counter_b = make_counter()
    output=== Closures ===
    A closure 'remembers' variables from enclosing scope
    even after the outer function has returned.
  2. count ← 0

    pass 1 of 2
    38def make_counter():  #?closure39    count→ 0 = 0  # Captured by inner function40    41    def counter():42        nonlocal count43        count += 144        return count45    46    return counter<function make_counter.<locals>.counter at ⟨addr A⟩>  # Return the inner function
  3. counter_a ← <function make_counter.<locals>.counter at ⟨addr A⟩>

    7# Create closures  #?create8counter_a→ <function make_counter.<locals>.counter at ⟨addr A⟩> = make_counter()9counter_b = make_counter()
  4. count ← 0

    pass 2 of 2
    38def make_counter():  #?closure39    count→ 0 = 0  # Captured by inner function40    41    def counter():42        nonlocal count43        count += 144        return count45    46    return counter<function make_counter.<locals>.counter at ⟨addr B⟩>  # Return the inner function
  5. counter_b ← <function make_counter.<locals>.counter at ⟨addr B⟩>

    8counter_a = make_counter()9counter_b→ <function make_counter.<locals>.counter at ⟨addr B⟩> = make_counter()1011print("Two independent counters:")12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")
    outputTwo independent counters:
  6. count ← 1

    pass 1 of 5
    41def counter():42    nonlocal count43    count→ 1 += 144    return count1
    All 5 passes — pass 1 is the card above
    passcount
    10 1
    21 2
    32 3
    40 1
    51 2
  7. print(f"counter_a(): {counter_a()}")

    11print("Two independent counters:")12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")14print(f"counter_a(): {counter_a()}")
    outputcounter_a(): 1
  8. print(f"counter_a(): {counter_a()}")

    12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")14print(f"counter_a(): {counter_a()}")15print(f"counter_b(): {counter_b()}")  # Independent!
    outputcounter_a(): 2
  9. print(f"counter_a(): {counter_a()}")

    13print(f"counter_a(): {counter_a()}")14print(f"counter_a(): {counter_a()}")15print(f"counter_b(): {counter_b()}")  # Independent!16print(f"counter_b(): {counter_b()}")
    outputcounter_a(): 3
  10. print(f"counter_b(): {counter_b()}") # Independent!

    14print(f"counter_a(): {counter_a()}")15print(f"counter_b(): {counter_b()}")  # Independent!16print(f"counter_b(): {counter_b()}")
    outputcounter_b(): 1
  11. double_factor ← 2, triple_factor ← 3

    15print(f"counter_b(): {counter_b()}")  # Independent!16print(f"counter_b(): {counter_b()}")1718print("\n=== Multiplier Factory ===")  #?factory19double_factor→ 2 = 2  #@double_factor=420triple_factor→ 3 = 3  #@triple_factor=521double = make_multiplier(double_factor2)22triple = make_multiplier(triple_factor)
    outputcounter_b(): 2
    
    === Multiplier Factory ===
  12. def make_multiplier(factor): #?capture # 'factor' is captured by …

    pass 1 of 2
    48def make_multiplier(factor2):  #?capture49    # 'factor' is captured by inner function50    def multiply(x):51        return x * factor  # Uses captured factor52    53    return multiply<function make_multiplier.<locals>.multiply at ⟨addr C⟩>
  13. double ← <function make_multiplier.<locals>.multiply at ⟨addr C⟩>

    20triple_factor = 3  #@triple_factor=521double→ <function make_multiplier.<locals>.multiply at ⟨addr C⟩> = make_multiplier(double_factor2)22triple = make_multiplier(triple_factor3)
  14. def make_multiplier(factor): #?capture # 'factor' is captured by …

    pass 2 of 2
    48def make_multiplier(factor3):  #?capture49    # 'factor' is captured by inner function50    def multiply(x):51        return x * factor  # Uses captured factor52    53    return multiply<function make_multiplier.<locals>.multiply at ⟨addr D⟩>
  15. triple ← <function make_multiplier.<locals>.multiply at ⟨addr D⟩>

    21double = make_multiplier(double_factor)22triple→ <function make_multiplier.<locals>.multiply at ⟨addr D⟩> = make_multiplier(triple_factor3)2324print(f"times {double_factor2}(5) = {double(5)}")25print(f"times {triple_factor}(5) = {triple(5)}")
  16. def multiply(x):

    pass 1 of 3
    49# 'factor' is captured by inner function50def multiply(x5):51    return x5 * factor2  # Uses captured factor
    All 3 passes — pass 1 is the card above
    passxfactor
    152
    253
    3102
  17. print(f"times {double_factor}(5) = {double(5)}")

    24print(f"times {double_factor2}(5) = {double(5)}")25print(f"times {triple_factor3}(5) = {triple(5)}")26print(f"times {double_factor}(10) = {double(10)}")
    outputtimes 2(5) = 10
  18. print(f"times {triple_factor}(5) = {triple(5)}")

    24print(f"times {double_factor}(5) = {double(5)}")25print(f"times {triple_factor3}(5) = {triple(5)}")26print(f"times {double_factor2}(10) = {double(10)}")
    outputtimes 3(5) = 15
  19. print(f"times {double_factor}(10) = {double(10)}")

    25print(f"times {triple_factor}(5) = {triple(5)}")26print(f"times {double_factor2}(10) = {double(10)}")2728#@var=_,!29print("\n=== Practical: Logger ===")30info_log = make_logger("INFO")31error_log = make_logger("ERROR")
    outputtimes 2(10) = 20
    
    === Practical: Logger ===
  20. def make_logger(level): #?practical

    pass 1 of 2
    55#@var=_,!56def make_logger(levelINFO):  #?practical57    """Factory for creating loggers with preset level."""58    def log(message):59        print(f"[{level}] {message}")60    return log<function make_logger.<locals>.log at ⟨addr E⟩>61#@var=_,!
  21. info_log ← <function make_logger.<locals>.log at ⟨addr E⟩>

    29print("\n=== Practical: Logger ===")30info_log→ <function make_logger.<locals>.log at ⟨addr E⟩> = make_logger("INFO")31error_log = make_logger("ERROR")
  22. def make_logger(level): #?practical

    pass 2 of 2
    55#@var=_,!56def make_logger(levelERROR):  #?practical57    """Factory for creating loggers with preset level."""58    def log(message):59        print(f"[{level}] {message}")60    return log<function make_logger.<locals>.log at ⟨addr F⟩>61#@var=_,!
  23. error_log ← <function make_logger.<locals>.log at ⟨addr F⟩>

    30info_log = make_logger("INFO")31error_log→ <function make_logger.<locals>.log at ⟨addr F⟩> = make_logger("ERROR")3233info_log("Application started")34info_log("Loading config")
  24. def log(message):

    pass 1 of 3
    33    info_log("Application started")34    info_log("Loading config")35    error_log("Connection failed!")36    #@var=_,!3738def make_counter():  #?closure39    count = 0  # Captured by inner function40    41    def counter():42        nonlocal count43        count += 144        return count45    46    return counter  # Return the inner function4748def make_multiplier(factor):  #?capture49    # 'factor' is captured by inner function50    def multiply(x):51        return x * factor  # Uses captured factor52    53    return multiply5455#@var=_,!56def make_logger(level):  #?practical57    """Factory for creating loggers with preset level."""58    def log(messageApplication started):59        print(f"[{levelINFO}] {messageApplication started}")60    return log
    output[INFO] Application started
    All 3 passes — pass 1 is the card above
    passmessagelevel
    1Application startedINFO
    2Loading configINFO
    3Connection failed!ERROR
  25. main()

    63if __name__ == "__main__":64    main()65#@help create
  1. def main():

    1def main():2    print("=== Closures ===\n")3    print("A closure 'remembers' variables from enclosing scope")4    print("even after the outer function has returned.\n")5    6    # Create closures7    counter_a = make_counter()8    counter_b = make_counter()
    output=== Closures ===
    A closure 'remembers' variables from enclosing scope
    even after the outer function has returned.
  2. count ← 0

    pass 1 of 2
    35def make_counter():36    count→ 0 = 0  # Captured by inner function37    38    def counter():39        nonlocal count40        count += 141        return count42    43    return counter<function make_counter.<locals>.counter at ⟨addr A⟩>  # Return the inner function
  3. counter_a ← <function make_counter.<locals>.counter at ⟨addr A⟩>

    6# Create closures7counter_a→ <function make_counter.<locals>.counter at ⟨addr A⟩> = make_counter()8counter_b = make_counter()
  4. count ← 0

    pass 2 of 2
    35def make_counter():36    count→ 0 = 0  # Captured by inner function37    38    def counter():39        nonlocal count40        count += 141        return count42    43    return counter<function make_counter.<locals>.counter at ⟨addr B⟩>  # Return the inner function
  5. counter_b ← <function make_counter.<locals>.counter at ⟨addr B⟩>

    7counter_a = make_counter()8counter_b→ <function make_counter.<locals>.counter at ⟨addr B⟩> = make_counter()910print("Two independent counters:")11print(f"counter_a(): {counter_a()}")12print(f"counter_a(): {counter_a()}")
    outputTwo independent counters:
  6. count ← 1

    pass 1 of 5
    38def counter():39    nonlocal count40    count→ 1 += 141    return count1
    All 5 passes — pass 1 is the card above
    passcount
    10 1
    21 2
    32 3
    40 1
    51 2
  7. print(f"counter_a(): {counter_a()}")

    10print("Two independent counters:")11print(f"counter_a(): {counter_a()}")12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")
    outputcounter_a(): 1
  8. print(f"counter_a(): {counter_a()}")

    11print(f"counter_a(): {counter_a()}")12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")14print(f"counter_b(): {counter_b()}")  # Independent!
    outputcounter_a(): 2
  9. print(f"counter_a(): {counter_a()}")

    12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")14print(f"counter_b(): {counter_b()}")  # Independent!15print(f"counter_b(): {counter_b()}")
    outputcounter_a(): 3
  10. print(f"counter_b(): {counter_b()}") # Independent!

    13print(f"counter_a(): {counter_a()}")14print(f"counter_b(): {counter_b()}")  # Independent!15print(f"counter_b(): {counter_b()}")
    outputcounter_b(): 1
  11. double_factor ← 4, triple_factor ← 3

    14print(f"counter_b(): {counter_b()}")  # Independent!15print(f"counter_b(): {counter_b()}")1617print("\n=== Multiplier Factory ===")18double_factor→ 4 = 419triple_factor→ 3 = 320double = make_multiplier(double_factor4)21triple = make_multiplier(triple_factor)
    outputcounter_b(): 2
    
    === Multiplier Factory ===
  12. def make_multiplier(factor): # 'factor' is captured by inner funct…

    pass 1 of 2
    45def make_multiplier(factor4):46    # 'factor' is captured by inner function47    def multiply(x):48        return x * factor  # Uses captured factor49    50    return multiply<function make_multiplier.<locals>.multiply at ⟨addr C⟩>
  13. double ← <function make_multiplier.<locals>.multiply at ⟨addr C⟩>

    19triple_factor = 320double→ <function make_multiplier.<locals>.multiply at ⟨addr C⟩> = make_multiplier(double_factor4)21triple = make_multiplier(triple_factor3)
  14. def make_multiplier(factor): # 'factor' is captured by inner funct…

    pass 2 of 2
    45def make_multiplier(factor3):46    # 'factor' is captured by inner function47    def multiply(x):48        return x * factor  # Uses captured factor49    50    return multiply<function make_multiplier.<locals>.multiply at ⟨addr D⟩>
  15. triple ← <function make_multiplier.<locals>.multiply at ⟨addr D⟩>

    20double = make_multiplier(double_factor)21triple→ <function make_multiplier.<locals>.multiply at ⟨addr D⟩> = make_multiplier(triple_factor3)2223print(f"times {double_factor4}(5) = {double(5)}")24print(f"times {triple_factor}(5) = {triple(5)}")
  16. def multiply(x):

    pass 1 of 3
    46# 'factor' is captured by inner function47def multiply(x5):48    return x5 * factor4  # Uses captured factor
    All 3 passes — pass 1 is the card above
    passxfactor
    154
    253
    3104
  17. print(f"times {double_factor}(5) = {double(5)}")

    23print(f"times {double_factor4}(5) = {double(5)}")24print(f"times {triple_factor3}(5) = {triple(5)}")25print(f"times {double_factor}(10) = {double(10)}")
    outputtimes 4(5) = 20
  18. print(f"times {triple_factor}(5) = {triple(5)}")

    23print(f"times {double_factor}(5) = {double(5)}")24print(f"times {triple_factor3}(5) = {triple(5)}")25print(f"times {double_factor4}(10) = {double(10)}")
    outputtimes 3(5) = 15
  19. print(f"times {double_factor}(10) = {double(10)}")

    24print(f"times {triple_factor}(5) = {triple(5)}")25print(f"times {double_factor4}(10) = {double(10)}")2627print("\n=== Practical: Logger ===")28info_log = make_logger("INFO")29error_log = make_logger("ERROR")
    outputtimes 4(10) = 40
    
    === Practical: Logger ===
  20. def make_logger(level):

    pass 1 of 2
    52def make_logger(levelINFO):53    """Factory for creating loggers with preset level."""54    def log(message):55        print(f"[{level}] {message}")56    return log<function make_logger.<locals>.log at ⟨addr E⟩>
  21. info_log ← <function make_logger.<locals>.log at ⟨addr E⟩>

    27print("\n=== Practical: Logger ===")28info_log→ <function make_logger.<locals>.log at ⟨addr E⟩> = make_logger("INFO")29error_log = make_logger("ERROR")
  22. def make_logger(level):

    pass 2 of 2
    52def make_logger(levelERROR):53    """Factory for creating loggers with preset level."""54    def log(message):55        print(f"[{level}] {message}")56    return log<function make_logger.<locals>.log at ⟨addr F⟩>
  23. error_log ← <function make_logger.<locals>.log at ⟨addr F⟩>

    28info_log = make_logger("INFO")29error_log→ <function make_logger.<locals>.log at ⟨addr F⟩> = make_logger("ERROR")3031info_log("Application started")32info_log("Loading config")
  24. def log(message):

    pass 1 of 3
    31    info_log("Application started")32    info_log("Loading config")33    error_log("Connection failed!")3435def make_counter():36    count = 0  # Captured by inner function37    38    def counter():39        nonlocal count40        count += 141        return count42    43    return counter  # Return the inner function4445def make_multiplier(factor):46    # 'factor' is captured by inner function47    def multiply(x):48        return x * factor  # Uses captured factor49    50    return multiply5152def make_logger(level):53    """Factory for creating loggers with preset level."""54    def log(messageApplication started):55        print(f"[{levelINFO}] {messageApplication started}")56    return log
    output[INFO] Application started
    All 3 passes — pass 1 is the card above
    passmessagelevel
    1Application startedINFO
    2Loading configINFO
    3Connection failed!ERROR
  25. main()

    58if __name__ == "__main__":59    main()
  1. def main():

    1def main():2    print("=== Closures ===\n")3    print("A closure 'remembers' variables from enclosing scope")4    print("even after the outer function has returned.\n")5    6    # Create closures7    counter_a = make_counter()8    counter_b = make_counter()
    output=== Closures ===
    A closure 'remembers' variables from enclosing scope
    even after the outer function has returned.
  2. count ← 0

    pass 1 of 2
    35def make_counter():36    count→ 0 = 0  # Captured by inner function37    38    def counter():39        nonlocal count40        count += 141        return count42    43    return counter<function make_counter.<locals>.counter at ⟨addr A⟩>  # Return the inner function
  3. counter_a ← <function make_counter.<locals>.counter at ⟨addr A⟩>

    6# Create closures7counter_a→ <function make_counter.<locals>.counter at ⟨addr A⟩> = make_counter()8counter_b = make_counter()
  4. count ← 0

    pass 2 of 2
    35def make_counter():36    count→ 0 = 0  # Captured by inner function37    38    def counter():39        nonlocal count40        count += 141        return count42    43    return counter<function make_counter.<locals>.counter at ⟨addr B⟩>  # Return the inner function
  5. counter_b ← <function make_counter.<locals>.counter at ⟨addr B⟩>

    7counter_a = make_counter()8counter_b→ <function make_counter.<locals>.counter at ⟨addr B⟩> = make_counter()910print("Two independent counters:")11print(f"counter_a(): {counter_a()}")12print(f"counter_a(): {counter_a()}")
    outputTwo independent counters:
  6. count ← 1

    pass 1 of 5
    38def counter():39    nonlocal count40    count→ 1 += 141    return count1
    All 5 passes — pass 1 is the card above
    passcount
    10 1
    21 2
    32 3
    40 1
    51 2
  7. print(f"counter_a(): {counter_a()}")

    10print("Two independent counters:")11print(f"counter_a(): {counter_a()}")12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")
    outputcounter_a(): 1
  8. print(f"counter_a(): {counter_a()}")

    11print(f"counter_a(): {counter_a()}")12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")14print(f"counter_b(): {counter_b()}")  # Independent!
    outputcounter_a(): 2
  9. print(f"counter_a(): {counter_a()}")

    12print(f"counter_a(): {counter_a()}")13print(f"counter_a(): {counter_a()}")14print(f"counter_b(): {counter_b()}")  # Independent!15print(f"counter_b(): {counter_b()}")
    outputcounter_a(): 3
  10. print(f"counter_b(): {counter_b()}") # Independent!

    13print(f"counter_a(): {counter_a()}")14print(f"counter_b(): {counter_b()}")  # Independent!15print(f"counter_b(): {counter_b()}")
    outputcounter_b(): 1
  11. double_factor ← 2, triple_factor ← 5

    14print(f"counter_b(): {counter_b()}")  # Independent!15print(f"counter_b(): {counter_b()}")1617print("\n=== Multiplier Factory ===")18double_factor→ 2 = 219triple_factor→ 5 = 520double = make_multiplier(double_factor2)21triple = make_multiplier(triple_factor)
    outputcounter_b(): 2
    
    === Multiplier Factory ===
  12. def make_multiplier(factor): # 'factor' is captured by inner funct…

    pass 1 of 2
    45def make_multiplier(factor2):46    # 'factor' is captured by inner function47    def multiply(x):48        return x * factor  # Uses captured factor49    50    return multiply<function make_multiplier.<locals>.multiply at ⟨addr C⟩>
  13. double ← <function make_multiplier.<locals>.multiply at ⟨addr C⟩>

    19triple_factor = 520double→ <function make_multiplier.<locals>.multiply at ⟨addr C⟩> = make_multiplier(double_factor2)21triple = make_multiplier(triple_factor5)
  14. def make_multiplier(factor): # 'factor' is captured by inner funct…

    pass 2 of 2
    45def make_multiplier(factor5):46    # 'factor' is captured by inner function47    def multiply(x):48        return x * factor  # Uses captured factor49    50    return multiply<function make_multiplier.<locals>.multiply at ⟨addr D⟩>
  15. triple ← <function make_multiplier.<locals>.multiply at ⟨addr D⟩>

    20double = make_multiplier(double_factor)21triple→ <function make_multiplier.<locals>.multiply at ⟨addr D⟩> = make_multiplier(triple_factor5)2223print(f"times {double_factor2}(5) = {double(5)}")24print(f"times {triple_factor}(5) = {triple(5)}")
  16. def multiply(x):

    pass 1 of 3
    46# 'factor' is captured by inner function47def multiply(x5):48    return x5 * factor2  # Uses captured factor
    All 3 passes — pass 1 is the card above
    passxfactor
    152
    255
    3102
  17. print(f"times {double_factor}(5) = {double(5)}")

    23print(f"times {double_factor2}(5) = {double(5)}")24print(f"times {triple_factor5}(5) = {triple(5)}")25print(f"times {double_factor}(10) = {double(10)}")
    outputtimes 2(5) = 10
  18. print(f"times {triple_factor}(5) = {triple(5)}")

    23print(f"times {double_factor}(5) = {double(5)}")24print(f"times {triple_factor5}(5) = {triple(5)}")25print(f"times {double_factor2}(10) = {double(10)}")
    outputtimes 5(5) = 25
  19. print(f"times {double_factor}(10) = {double(10)}")

    24print(f"times {triple_factor}(5) = {triple(5)}")25print(f"times {double_factor2}(10) = {double(10)}")2627print("\n=== Practical: Logger ===")28info_log = make_logger("INFO")29error_log = make_logger("ERROR")
    outputtimes 2(10) = 20
    
    === Practical: Logger ===
  20. def make_logger(level):

    pass 1 of 2
    52def make_logger(levelINFO):53    """Factory for creating loggers with preset level."""54    def log(message):55        print(f"[{level}] {message}")56    return log<function make_logger.<locals>.log at ⟨addr E⟩>
  21. info_log ← <function make_logger.<locals>.log at ⟨addr E⟩>

    27print("\n=== Practical: Logger ===")28info_log→ <function make_logger.<locals>.log at ⟨addr E⟩> = make_logger("INFO")29error_log = make_logger("ERROR")
  22. def make_logger(level):

    pass 2 of 2
    52def make_logger(levelERROR):53    """Factory for creating loggers with preset level."""54    def log(message):55        print(f"[{level}] {message}")56    return log<function make_logger.<locals>.log at ⟨addr F⟩>
  23. error_log ← <function make_logger.<locals>.log at ⟨addr F⟩>

    28info_log = make_logger("INFO")29error_log→ <function make_logger.<locals>.log at ⟨addr F⟩> = make_logger("ERROR")3031info_log("Application started")32info_log("Loading config")
  24. def log(message):

    pass 1 of 3
    31    info_log("Application started")32    info_log("Loading config")33    error_log("Connection failed!")3435def make_counter():36    count = 0  # Captured by inner function37    38    def counter():39        nonlocal count40        count += 141        return count42    43    return counter  # Return the inner function4445def make_multiplier(factor):46    # 'factor' is captured by inner function47    def multiply(x):48        return x * factor  # Uses captured factor49    50    return multiply5152def make_logger(level):53    """Factory for creating loggers with preset level."""54    def log(messageApplication started):55        print(f"[{levelINFO}] {messageApplication started}")56    return log
    output[INFO] Application started
    All 3 passes — pass 1 is the card above
    passmessagelevel
    1Application startedINFO
    2Loading configINFO
    3Connection failed!ERROR
  25. main()

    58if __name__ == "__main__":59    main()

A closure "remembers" variables from where it was defined, not where it's called.

See the Closure Boxes

A closure is a function plus the values it captured. These diagrams pin the exact counters and multipliers from closure.py.

Each returned function keeps its own factorEach returned function keeps its own factormake_multiplier(2)double: factor=2double(5)=10make_multiplier(3)triple: factor=3triple(5)=15
`double` remembers factor=2, while `triple` remembers factor=3. Calling both with 5 produces different results because their captured boxes are different.
Two counters keep separate captured stateTwo counters keep separate captured statecallcounter_acounter_bstart00a() #110a() #220a() #330b() #131
Three calls to `counter_a` advance only counter_a's captured `count`; `counter_b` starts from its own count and returns 1.
closure Function that captures variables from enclosing scope. Remembers them for later.

Exercise: scope_gotchas.py

Explore common scope-related mistakes and how to avoid them