한양과학기술고등학교

에듀테크 프로젝트 대시보드

스코프 · 클로저

LEGB global nonlocal closure

이름을 찾는 순서: Local → Enclosing → Global → Built-in.

1. LEGB 규칙

x = "global"

def outer():
    x = "enclosing"
    def inner():
        # x 를 안 만들었으므로 바깥에서 찾음
        print(x)       # => 'enclosing'
    inner()

len          # built-in (아무 데도 정의 안 했지만 찾음)

2. 읽기는 되지만 쓰기는 새 지역변수

count = 0

def increment():
    count += 1        # UnboundLocalError!
    # 대입이 있으면 count 를 지역변수로 간주 → 오른쪽 count 가 미정의

def increment():
    global count      # "바깥 count 를 쓰겠다"
    count += 1

3. nonlocal

def make_counter():
    count = 0
    def tick():
        nonlocal count       # enclosing 스코프의 count
        count += 1
        return count
    return tick

c = make_counter()
c(); c(); c()               # => 1, 2, 3

4. 클로저

def multiplier(factor):
    def multiply(n):
        return n * factor    # factor 를 "기억"
    return multiply

double = multiplier(2)
triple = multiplier(3)
double(10)     # => 20
triple(10)     # => 30

double.__closure__[0].cell_contents   # => 2  (갇힌 값)

5. 루프 변수 캐치 함정

# 흔한 버그: 모든 함수가 마지막 i 를 참조
funcs = [lambda: i for i in range(3)]
[f() for f in funcs]           # => [2, 2, 2]  (기대: 0,1,2)

# 해결 1: 기본 인자로 현재 값 고정
funcs = [lambda i=i: i for i in range(3)]   # => [0, 1, 2]

# 해결 2: 팩토리 함수
def make(i):
    return lambda: i
funcs = [make(i) for i in range(3)]

6. 스코프 관련 함수

def f():
    a = 1
    print(locals())     # {'a': 1}

print(globals().keys())  # 모듈 전역

x = 10
def g():
    exec("x = 99")      # locals 수정은 반영 안 될 수 있음 — 사용 자제
"지금 하는 것이 아예 안 하는 것보다 낫다 — 다만 '지금 당장'보다는 낫지 않을 때가 많다"