본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

범위와 클로저 — 함수가 바깥 값을 기억하는 법

~22 min · closure, scope, LEGB, nonlocal, global

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

이름은 LEGB 순서로 찾는다

Python은 현재 함수의 Local, 감싸는 함수의 Enclosing, 모듈의 Global, 내장 이름 Built-in 순서로 이름을 찾아. 안쪽에서 이름에 대입하면 기본적으로 새 지역 이름을 만드는 점이 중요해.

클로저는 바깥 바인딩을 붙잡는다

함수 안에서 만든 함수가 바깥 함수의 이름을 사용하면, 바깥 호출이 끝난 뒤에도 그 바인딩을 기억해. 설정을 담은 함수나 작은 상태 기계를 클래스 없이 만들 수 있어.

nonlocal과 global

nonlocal은 가장 가까운 감싸는 함수의 이름을 다시 묶고, global은 모듈 이름을 다시 묶어. 모듈 전역 변경은 의존성을 숨기기 쉬우므로 드물게 써야 해.

반복문에서 만든 클로저의 늦은 바인딩

클로저는 값을 복사하기보다 이름을 기억하므로, 반복이 끝난 뒤 모두 마지막 값을 볼 수 있어. 기본 인자나 별도 팩토리 호출로 각 차례의 값을 고정해.

Code

LEGB 이름 탐색 순서·python
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)            # local 먼저 찾음
    inner()

outer()                     # local

# inner 에 x 정의 안 하면 바깥으로 걸음
def outer2():
    x = "enclosing"
    def inner():
        print(x)            # enclosing 찾음
    inner()

outer2()                    # enclosing

# 둘 다 정의 안 하면 global 찾음
def outer3():
    def inner():
        print(x)            # global
    inner()

outer3()                    # global
설정을 기억하는 클로저 팩토리·python
def make_counter(start=0):
    count = start
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c1 = make_counter()
c2 = make_counter(100)

print(c1())     # 1
print(c1())     # 2
print(c1())     # 3

print(c2())     # 101
print(c2())     # 102
# c1 / c2 가 각자 자기 count 를 가짐 — closure 는 독립적
nonlocal로 바깥 상태 바꾸기·python
def outer():
    n = 10
    def inner():
        n = 99             # 새 local — 바깥 n 안 건드림
        print("inner sees:", n)
    inner()
    print("outer still:", n)

outer()
# inner sees: 99
# outer still: 10

# nonlocal 로
def outer2():
    n = 10
    def inner():
        nonlocal n
        n = 99             # 바깥 n 변경
    inner()
    print("outer now:", n)

outer2()                   # outer now: 99
반복문 클로저의 늦은 바인딩·python
# 함정
fns = []
for i in range(5):
    fns.append(lambda: i)

print([f() for f in fns])      # [4, 4, 4, 4, 4]   <- 다 같음

# 왜? 각 lambda 가 같은 i 를 참조, loop 끝나면 4.

# 해결 1 — 디폴트 인자가 *정의 시점* 에 값 캡쳐
fns = []
for i in range(5):
    fns.append(lambda i=i: i)

print([f() for f in fns])      # [0, 1, 2, 3, 4]

# 해결 2 — closure factory
def make(i):
    return lambda: i

fns = [make(i) for i in range(5)]
print([f() for f in fns])      # [0, 1, 2, 3, 4]
global을 아껴 써야 하는 이유·python
counter = 0

def bump():
    global counter
    counter += 1

bump()
bump()
bump()
print(counter)             # 3

# 근데 — `global` 쓰는 건 보통 class / closure / 명시적 인자로 가야 한다는 신호.
# 아껴 써.

External links

Exercise

호출할 때마다 받은 숫자를 누적하고 새 합계를 돌려주는 함수를 만드는 make_accumulator()를 작성해. 클로저와 nonlocal을 쓰고 클래스는 쓰지 마. 누산기 두 개를 만들어 여러 번 호출한 뒤 서로의 합계가 섞이지 않는지 보여줘.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.