Skip to content

Scoping Rules

클로저와 관련된 Python의 스코프 규칙입니다.

Comprehension Scoping (Python 3+)

List Comprehension Has Own Scope

x = "outer"
result = [x for x in range(3)]

print(x)  # "outer" (unchanged)
print(result)  # [0, 1, 2]

Python 3에서 comprehension의 루프 변수는 leak하지 않습니다.

Dict/Set Comprehensions — Same Behavior

x = 10
d = {x: x**2 for x in range(3)}

print(x)  # 10 (unchanged)
print(d)  # {0: 0, 1: 1, 2: 4}

Nested Comprehensions

matrix = [[j for j in range(3)] for i in range(3)]
# i and j don't leak

Generator Scoping

Generator Expressions Have Own Scope

x = 10
gen = (x for x in range(3))

print(x)  # 10 (unchanged)
print(list(gen))  # [0, 1, 2]

Generators Can Close Over Variables

def make_counter(start):
    count = start

    def counter():
        nonlocal count
        while True:
            count += 1
            yield count

    return counter()

gen = make_counter(0)
print(next(gen))  # 1
print(next(gen))  # 2

Lambda vs def

Both Support Closures Identically

def outer():
    x = 10

    # Lambda closure
    f1 = lambda: x

    # Def closure
    def f2():
        return x

    return f1, f2

a, b = outer()
print(a())  # 10
print(b())  # 10

Differences

Feature lambda def
Body Single expression Multiple statements
Name Anonymous (<lambda>) Named
Docstring No Yes
Decorators No Yes
Closures ✅ Same ✅ Same

When to Use Each

# Lambda: simple, inline
sorted(items, key=lambda x: x.name)

# Def: complex logic, needs name
def process_item(item):
    """Process and validate item."""
    if not item.valid:
        raise ValueError("Invalid")
    return item.transform()

Nested Closures

Multiple Levels

def outer():
    x = 1

    def middle():
        y = 2

        def inner():
            return x + y  # Captures from both levels

        return inner

    return middle()

f = outer()
print(f())  # 3

Closure Composition

def add(x):
    def adder(y):
        return x + y
    return adder

add5 = add(5)
add10 = add(10)

print(add5(3))   # 8
print(add10(3))  # 13

Advanced: Scope Interaction

global vs nonlocal

x = "global"

def outer():
    x = "outer"

    def use_global():
        global x
        return x

    def use_nonlocal():
        nonlocal x
        return x

    print(use_global())    # "global"
    print(use_nonlocal())  # "outer"

outer()
Keyword Refers To
global Module-level variable
nonlocal Nearest enclosing function's variable

Class Scope Is Not Enclosing

class MyClass:
    x = 10

    def method(self):
        # x is NOT accessible here without self or MyClass
        # print(x)  # NameError
        print(MyClass.x)  # Works
        print(self.x)     # Works

Class body는 enclosing scope로 작동하지 않습니다.


Python 2 vs 3 Differences

Feature Python 2 Python 3
List comprehension leak Yes No
nonlocal keyword No Yes
Workaround for rebinding Mutable container nonlocal
# Python 2 style (still works in 3)
def counter():
    count = [0]
    def inc():
        count[0] += 1
        return count[0]
    return inc

# Python 3 style (preferred)
def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

Summary

Scope Type Creates Own Namespace Variables Leak
Function Yes No
Comprehension (Py3) Yes No
Generator expression Yes No
Class body Yes (but not enclosing) N/A
Loop (for/while) No Yes

Key Points: - Comprehensions and generators have isolated scopes in Python 3 - Lambda and def have identical closure behavior - Use nonlocal for rebinding, global for module-level - Class bodies don't create enclosing scopes for methods