Python 강좌 : 제 40강 - 클로저
클로저(Closure)
클로저(Closure)란 함수가 내부 함수를 포함하고 있는 형태를 의미하며, 내부 함수가 자신을 감싸고 있는 외부 함수의 지역 변수를 참조하는 형태입니다.
외부 함수의 실행이 종료되어도 내부 함수가 참조하는 외부 함수의 지역 변수는 사라지지 않고, 내부 함수에서 계속 사용할 수 있습니다.
일반적인 함수의 지역 변수는 함수가 호출될 때 만들어지고, 함수가 return으로 끝나면 함께 사라집니다. 제 20강에서 다룬 사용자 정의 함수는 호출이 끝나면 내부에서 만든 값을 기억하지 못합니다.
클로저는 이 규칙에 예외를 만듭니다. 내부 함수가 바깥 함수의 변수를 사용하고 있다면, Python은 그 변수를 버리지 않고 내부 함수에 붙여서 함께 보관합니다.
그러므로 클로저는 함수 + 함수가 만들어질 때의 주변 환경(변수)을 하나로 묶은 객체라고 볼 수 있습니다.
클로저의 정의는 다음과 같은 조건을 만족해야 합니다.
중첩 함수(Nested Function)형태내부 함수는외부 함수의 지역 변수를 참조하는 형태외부 함수는내부 함수를 반환하는 형태
클로저를 사용하면, 전역 변수의 사용을 최소화할 수 있으며, 데이터를 은닉하여 지속성을 보장할 수 있습니다.
또한, 외부에서 직접 접근할 수 없는 범위의 데이터를 내부 함수를 통해서 사용할 수 있습니다.
클로저는 설정값을 미리 고정한 함수를 만들어 내는 함수 팩토리(Function Factory), 호출 횟수처럼 상태를 기억하는 함수, 그리고 제 47강에서 다룰 데코레이터(Decorator)의 기반이 되는 개념입니다.
이번 강좌에서는 클로저를 만드는 방법, 바깥 변수를 읽고 수정하는 규칙(nonlocal), 클로저가 변수를 실제로 보관하는 셀(cell) 객체, 그리고 자주 하는 실수인 늦은 바인딩(Late Binding)을 순서대로 다룹니다.
클로저(Closure) 사용하기
클로저를 만들려면 함수 안에 함수를 정의하고, 안쪽 함수가 바깥 함수의 변수를 사용하도록 작성한 다음, 바깥 함수가 안쪽 함수를 호출하지 않고 함수 객체 그대로 반환하면 됩니다.
반환된 내부 함수는 이후에 몇 번이든 호출할 수 있으며, 호출할 때마다 바깥 함수가 남겨 둔 값을 사용합니다.
def outer_func(x):
total = x
def inner_func(y):
return total + y
return inner_func
main = outer_func(1)
sub1 = main(2)
sub2 = main(3)
sub3 = main(4)
print(sub1, sub2, sub3)- 결과
- 3 4 5
main 변수에 outer_func의 x(total) 값을 1로 정의하고, inner_func를 반환합니다.
main 변수에 inner_func가 할당되며, inner_func의 y 값을 각각 2, 3, 4로 할당합니다.
outer_func의 실행은 이미 끝났지만, inner_func 함수 기준에서 바깥 범위에 있는 total 변수를 결과에서 확인할 수 있듯이 계속 참조할 수 있습니다.
total 변수는 inner_func에서 사용되지만, 전역 변수도 아니며 inner_func 내부에서 정의하지 않은 변수입니다.
이 변수를 자유 변수(free variable)라 합니다.
실행 순서를 단계별로 따라가면 다음과 같습니다.
outer_func(1)이 호출되면 지역 변수x에 1이 들어가고,total = x로total도 1이 됩니다.def inner_func(y):구문이 실행되면서 함수 객체가 만들어집니다. 이때inner_func는total을 사용하므로, Python은total을 담은 셀(cell) 을 함수 객체에 연결합니다.return inner_func로 함수 객체가 반환되고outer_func의 실행은 끝납니다. 하지만 셀은inner_func가 붙잡고 있으므로 사라지지 않습니다.main(2)를 호출하면inner_func의 본문이 실행되고, 셀에서total의 값 1을 꺼내1 + 2 = 3을 반환합니다.main(3),main(4)도 같은 셀을 사용하므로 4, 5가 됩니다.
중첩 함수(Nested Function)가 아닌 형태에서는 전역 변수를 사용하게 되어, 코드가 복잡해질 경우 어디서 변경되었는지 확인하기 어려워집니다.
이를 방지하고자 클로저(Closure)를 사용합니다. 즉, 전역 변수 대신에 자유 변수를 사용합니다.
def outer_func(x):
total = x
def inner_func(y):
return total + y
return inner_func
print(outer_func.__code__.co_cellvars)
add1 = outer_func(1)
add10 = outer_func(10)
print(add1.__code__.co_freevars)
print(add1.__code__ is add10.__code__)
print(add1.__closure__[0] is add10.__closure__[0])
print(add1.__closure__[0].cell_contents, add10.__closure__[0].cell_contents)
print(add1(5), add10(5))- 결과
- (‘total’,)
(‘total’,)
True
False
1 10
6 15
같은 outer_func로 만든 두 클로저가 실제로 무엇을 공유하고 무엇을 따로 가지는지 확인하는 예제입니다.
첫 줄의 ('total',)은 outer_func.__code__.co_cellvars로, 바깥 함수 쪽에서 내부 함수에게 넘겨 주기 위해 셀로 만드는 변수의 이름입니다.
두 번째 줄의 ('total',)은 add1.__code__.co_freevars로, 내부 함수 쪽에서 바깥에서 받아 쓰는 자유 변수의 이름입니다. 같은 변수를 바깥 함수는 셀 변수, 내부 함수는 자유 변수로 부릅니다.
세 번째 줄의 True는 add1과 add10이 같은 코드 객체를 공유한다는 뜻입니다. 함수 본문(명령어)은 한 번만 컴파일되어 재사용됩니다.
네 번째 줄의 False는 두 클로저가 서로 다른 셀을 가진다는 뜻입니다. outer_func가 호출될 때마다 새로운 total 셀이 만들어집니다.
그러므로 다섯 번째 줄처럼 add1의 셀에는 1, add10의 셀에는 10이 담겨 있고, 같은 인수 5를 전달해도 6과 15로 결과가 달라집니다.
즉, 클로저는 코드는 공유하고, 변수(셀)는 호출마다 따로 가지는 구조입니다.
def make_multiplier(n):
def multiply(x):
return x * n
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5), triple(5))
print(list(map(double, [1, 2, 3])))- 결과
- 10 15
[2, 4, 6]
클로저의 대표적인 활용인 함수 팩토리(Function Factory) 예제입니다.
make_multiplier(2)는 n이 2로 고정된 곱셈 함수를, make_multiplier(3)은 n이 3으로 고정된 곱셈 함수를 만들어 반환합니다.
반환된 double과 triple은 매개변수가 x 하나뿐인 평범한 함수처럼 사용할 수 있으므로, map()처럼 인수가 하나인 함수를 요구하는 곳에도 그대로 전달할 수 있습니다.
설정값(n)과 실제 입력(x)을 호출 시점을 나눠서 전달할 수 있다는 점이 클로저의 장점입니다.
지역 변수(local variable) 참조
내부 함수는 바깥 함수의 변수를 읽기만 할 때는 아무 선언 없이 사용할 수 있습니다.
하지만 바깥 함수의 변수에 새 값을 대입하려고 하면 결과가 달라집니다. 이 차이는 Python이 변수의 범위(스코프)를 결정하는 규칙 때문입니다.
def outer_func():
value = 0
def inner_func():
value = 100
inner_func()
return value
func = outer_func()
print(func)- 결과
- 0
outer_func 함수에서 정의된 value 변수를 inner_func에서 변경을 시도한다면, 값이 정상적으로 변경되지 않습니다.
함수 내부에서 변수에 값을 할당하면, Python은 그 변수를 해당 함수의 새로운 지역 변수로 취급하기 때문입니다.
즉, inner_func 함수에서 value 변수가 새로 정의됐다는 의미가 됩니다.
위 코드에서 value 변수는 outer_func 함수의 value와 inner_func 함수의 value로 두 가지가 정의된 형태입니다.
inner_func()가 실행되면 inner_func만의 value에 100이 들어갔다가 함수가 끝나면서 사라지고, outer_func의 value는 처음 값인 0 그대로 반환됩니다.
이 판단은 실행 중이 아니라 함수를 컴파일할 때 이루어집니다. 함수 본문 어디에든 value = ... 형태의 대입이 있으면, 그 함수 안의 value는 처음부터 끝까지 지역 변수로 취급됩니다.
- Tip : 코드 편집기의 린터(Linter)에서는 이 코드에 대해
Unused variable 'value'(사용되지 않는 변수) 형태의 경고를 표시합니다. 이는 실행 오류가 아니라 정적 분석 도구의 경고입니다.
만약, 외부 함수의 지역 변수를 수정하려면, 다음과 같이 사용합니다.
def outer_func():
value = 0
def inner_func():
nonlocal value
value = 100
inner_func()
return value
func = outer_func()
print(func)- 결과
- 100
nonlocal value의 형태로 사용하면, 내부 함수의 value는 지역 변수로 새로이 생성되지 않고, 외부 함수의 value로 인식합니다.
nonlocal 키워드는 전역 범위를 제외한 가장 가까운 바깥 함수의 변수를 참조하므로, outer_func의 value(0)를 참조하게 됩니다.
그러므로 value = 100은 새 변수를 만드는 대신 outer_func의 value가 담긴 셀의 내용을 100으로 바꾸고, outer_func는 바뀐 값 100을 반환합니다.
nonlocal 선언은 대입하기 전에, 그 함수 본문의 앞부분에 작성합니다.
중첩이 더 깊어질 경우, 한 계단씩 바깥 함수로 올라가면서 변수를 찾게됩니다.
- Tip :
nonlocal은 바깥 함수의 변수만 가리킬 수 있습니다. 모듈 수준의 전역 변수를 수정하려면global키워드를 사용합니다.
def a():
x = "a"
def b():
def c():
nonlocal x
x = "c"
c()
b()
return x
print(a())- 결과
- c
함수가 세 겹으로 중첩된 경우입니다. c 함수의 nonlocal x는 먼저 바로 바깥인 b에서 x를 찾습니다.
b에는 x가 없으므로 한 단계 더 올라가 a의 x를 찾고, 그 변수를 "c"로 바꿉니다. 그러므로 a()는 c를 반환합니다.
src = """
def outer():
def inner():
nonlocal x
x = 1
"""
try:
compile(src, "<test>", "exec")
except SyntaxError as e:
print(type(e).__name__, ":", e.msg)- 결과
- SyntaxError : no binding for nonlocal ‘x’ found
바깥 함수 어디에도 해당 이름의 변수가 없으면 nonlocal은 사용할 수 없습니다.
이 오류는 실행 중이 아니라 코드를 컴파일하는 단계에서 발생하는 SyntaxError이므로, 예제에서는 compile() 함수로 코드를 컴파일해 오류를 확인했습니다.
global은 전역 변수가 아직 없어도 선언할 수 있지만, nonlocal은 반드시 이미 존재하는 바깥 함수의 변수를 가리켜야 합니다.
| 키워드 | 대상 | 선언 위치의 변수가 없을 때 |
|---|---|---|
| 선언 없음 | 읽기만 하면 바깥 변수, 대입하면 새 지역 변수 | - |
| nonlocal | 가장 가까운 바깥 함수의 변수 | SyntaxError |
| global | 모듈 수준의 전역 변수 | 대입하면 새 전역 변수 생성 |
nonlocal을 활용한 상태 유지
nonlocal을 이용하면 호출할 때마다 값이 누적되는 함수를 만들 수 있습니다.
전역 변수로도 같은 일을 할 수 있지만, 클로저를 사용하면 상태가 함수 안에 숨겨져 있어 다른 코드가 실수로 값을 바꿀 수 없고, 독립된 상태를 여러 개 만들 수 있습니다.
def counter():
count = 0
def increase():
nonlocal count
count += 1
return count
return increase
c1 = counter()
c2 = counter()
print(c1(), c1(), c1())
print(c2())- 결과
- 1 2 3
1
nonlocal을 사용하면 클로저가 상태(state)를 가지는 함수를 만들 수 있습니다.
counter()를 호출할 때마다 새로운 count 변수가 만들어지므로, c1과 c2는 서로 독립된 값을 유지합니다.
첫 줄은 c1을 세 번 호출한 결과입니다. 같은 셀의 값이 0 → 1 → 2 → 3으로 바뀌므로 1 2 3이 출력됩니다.
두 번째 줄의 c2()는 별도의 셀을 가지고 있으므로, c1이 몇 번 호출되었는지와 관계없이 1부터 시작합니다.
만약 nonlocal count를 생략하면 count += 1은 count = count + 1로 해석되어, 값을 읽기 전에 할당하려는 지역 변수로 간주됩니다.
이 경우 UnboundLocalError: cannot access local variable 'count' where it is not associated with a value 오류가 발생합니다.
def counter():
count = 0
def increase():
count += 1
return count
return increase
c = counter()
try:
c()
except UnboundLocalError as e:
print(type(e).__name__, ":", e)- 결과
- UnboundLocalError : cannot access local variable ‘count’ where it is not associated with a value
nonlocal 선언을 빠뜨린 경우의 실제 오류입니다.
count += 1에 대입이 포함되어 있으므로 increase 안의 count는 지역 변수가 됩니다. 그런데 count + 1을 계산하려면 먼저 지역 변수 count를 읽어야 하고, 아직 값이 없으므로 오류가 발생합니다.
counter()를 호출하는 시점에는 오류가 나지 않고, 반환된 c()를 호출하는 시점에 오류가 발생한다는 점도 함께 기억합니다.
def counter():
count = 0
def increase():
nonlocal count
count += 1
return count
return increase
c1 = counter()
cell = c1.__closure__[0]
print(cell.cell_contents)
c1()
print(cell.cell_contents)
c1()
print(cell.cell_contents)- 결과
- 0
1
2
상태가 실제로 어디에 저장되는지 셀을 직접 들여다보는 예제입니다.
counter()가 끝난 직후 셀에는 초깃값 0이 들어 있습니다. c1()을 한 번 호출할 때마다 nonlocal count로 지정된 셀의 내용이 바뀌어 1, 2로 증가합니다.
즉, nonlocal 변수에 대입하는 것은 셀 안의 값을 교체하는 동작이며, 셀 자체는 클로저가 만들어질 때부터 같은 객체입니다.
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
def counter():
count = 0
def increase():
nonlocal count
count += 1
return count
return increase
c1 = Counter()
c2 = counter()
print(c1(), c1(), c1.count)
print(c2(), c2(), c2.__closure__[0].cell_contents)- 결과
- 1 2 2
1 2 2
같은 카운터를 제 27강의 클래스와 클로저로 각각 구현한 예제입니다. __call__() 메서드를 정의한 인스턴스는 함수처럼 호출할 수 있습니다.
두 방식 모두 호출할 때마다 1, 2를 반환하며, 상태가 2로 유지됩니다.
클래스는 c1.count처럼 상태에 외부에서 쉽게 접근하고 수정할 수 있고, 메서드를 여러 개 둘 수 있습니다.
클로저는 상태에 접근하는 공식적인 방법이 없어 은닉성이 높고, 짧은 코드로 동작 하나만 가진 상태 있는 함수를 만들 때 간결합니다.
- Tip : 상태와 동작이 하나뿐이라면 클로저, 상태를 여러 개 다루거나 메서드가 여러 개 필요하다면 클래스를 사용하는 것이 읽기 좋습니다.
지역 변수(local variable) 변경
nonlocal이 필요한 것은 변수 이름에 새 객체를 대입할 때입니다.
자유 변수가 리스트나 사전 같은 변경 가능한(mutable) 객체라면, 이름은 그대로 둔 채 객체의 내용만 바꿀 수 있으므로 nonlocal 없이도 상태를 누적할 수 있습니다.
def outer_func():
var = list()
def inner_func(value):
var.append(value)
return var
return inner_func
main = outer_func()
result = main({"A": 65})
print(result)
result = main({"B": 66})
print(result)- 결과
- [{‘A’: 65}]
[{‘A’: 65}, {‘B’: 66}]
외부 함수의 지역 변수를 변경하게 되면, 값이 지속적으로 유지됩니다.
이 예제는 var 변수 자체를 다시 할당하지 않고, 리스트 객체의 append() 메서드로 내용만 변경하므로 nonlocal 키워드가 필요하지 않습니다.
첫 번째 호출에서 셀이 가리키는 리스트에 {"A": 65}가 추가되고, 두 번째 호출에서는 같은 리스트에 {"B": 66}이 이어서 추가됩니다.
inner_func는 매번 같은 리스트 객체를 반환하므로, 첫 번째 result와 두 번째 result는 사실 동일한 객체입니다. 반환된 리스트를 외부에서 수정하면 클로저 안의 상태도 함께 바뀌므로 주의합니다.
클로저를 통해서 전역 변수를 사용하지 않고, 외부 함수의 자유 변수를 통해서도 구현이 가능합니다.
- Tip : 반대로
var = var + [value]처럼 새 리스트를 만들어 대입하면 대입이 발생하므로nonlocal var선언이 필요합니다.
자유 변수(free variable)와 셀(cell)
클로저가 바깥 변수를 “기억한다”는 것은 실제로는 셀(cell) 객체를 통해 이루어집니다.
셀은 값 하나를 담는 작은 상자로, 바깥 함수와 내부 함수가 같은 셀을 함께 가리킵니다. 그러므로 바깥 함수가 끝나도 셀은 내부 함수가 붙잡고 있는 한 사라지지 않습니다.
def outer_func():
var = list()
def inner_func(value):
var.append(value)
return var
return inner_func
main = outer_func()
result = main({"A": 65})
result = main({"B": 66})
print(main.__closure__)
print(type(main.__closure__), len(main.__closure__))
print(main.__closure__[0].cell_contents)
print(main.__code__.co_freevars)- 결과
- (<cell at 0x0000025EA55D5C30: list object at 0x0000025EA55E2400>,)
<class ‘tuple’> 1
[{‘A’: 65}, {‘B’: 66}]
(‘var’,)
제 39강에서 다룬 특수 속성(attributes) 중 하나인 __closure__를 통해 자유 변수를 확인할 수 있습니다.
클로저 속성은 셀(cell) 형태로 스코프(Scope)에서 참조하는 변수를 보여줍니다.
스코프(Scope)란 정의된 변수가 보이는 유효 범위를 의미합니다.
즉, main 함수의 셀은 outer_func 함수 내에서 정의된 변수를 보여줍니다.
__closure__는 셀 객체로 이루어진 튜플이며, 자유 변수의 개수만큼 길이를 가집니다. 자유 변수가 없는 일반 함수라면 None입니다.
외부 함수의 자유 변수의 개수는 1개이므로, 셀의 0 번째 값에 var 변수가 담겨있습니다.
main.__closure__[0]는 0 번째 셀을 출력합니다.
첫 줄의 <cell at ...: list object at ...>은 “셀이 리스트 객체를 담고 있다”는 뜻입니다. 앞의 주소는 셀 자신의 주소, 뒤의 주소는 셀이 가리키는 리스트의 주소입니다.
실제 값을 확인하기 위해서는 cell_contents를 사용해 자유 변수의 값을 확인할 수 있습니다.
자유 변수의 이름은 __code__.co_freevars로 확인할 수 있으며, __closure__의 셀과 같은 순서로 나열됩니다.
- Tip :
0x0000025EA55D5C30과 같은 메모리 주소는 실행할 때마다, 또 실행 환경마다 달라집니다.
def outer():
def inner(y):
return y * 2
return inner
print(outer().__closure__)- 결과
- None
함수 안에 정의되어 반환되더라도, 내부 함수가 바깥 함수의 변수를 하나도 사용하지 않는다면 셀이 만들어지지 않으므로 __closure__는 None입니다.
즉, 클로저인지 아닌지는 중첩 여부가 아니라 자유 변수를 참조하는지로 결정됩니다. 클로저의 세 가지 조건 중 두 번째 조건이 빠진 경우입니다.
늦은 바인딩(Late Binding)
클로저에서 가장 자주 하는 실수는 반복문 안에서 여러 개의 함수를 만들 때 발생합니다.
각 함수가 만들어질 때의 값을 기억할 것이라고 기대하지만, 실제로는 모두 같은 변수를 바라보기 때문에 예상과 다른 결과가 나옵니다.
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])- 결과
- [2, 2, 2]
[0, 1, 2]
클로저는 자유 변수의 값이 아니라 변수 자체를 참조합니다.
그러므로 자유 변수의 값은 내부 함수가 정의될 때가 아니라 호출될 때 조회됩니다. 이를 늦은 바인딩(Late Binding)이라 합니다.
첫 번째 예제의 세 함수는 모두 같은 변수 i를 참조하며, 호출 시점에 i는 이미 2이므로 모두 2를 반환합니다.
정의 시점의 값을 고정하려면 두 번째 예제처럼 기본값 매개변수(i=i)로 값을 전달합니다.
기본값은 함수를 정의하는 순간 계산되어 함수 객체에 저장되므로, 각 람다는 자신이 만들어질 때의 i 값(0, 1, 2)을 따로 가지게 됩니다.
funcs = [lambda: i for i in range(3)]
print(funcs[0].__closure__[0] is funcs[2].__closure__[0])
print(funcs[0].__closure__[0].cell_contents)
def make(i):
return lambda: i
funcs = [make(i) for i in range(3)]
print([f() for f in funcs])
print([f.__closure__[0].cell_contents for f in funcs])- 결과
- True
2
[0, 1, 2]
[0, 1, 2]
첫 줄의 True는 첫 번째 람다와 세 번째 람다가 같은 셀을 공유한다는 것을 보여 줍니다. 반복이 끝난 뒤 그 셀에는 마지막 값 2가 들어 있습니다.
두 번째 방법은 값을 고정하는 함수 팩토리 make(i)를 사용하는 것입니다. make()가 호출될 때마다 새로운 i 셀이 만들어지므로, 각 람다는 서로 다른 셀에 0, 1, 2를 따로 보관합니다.
기본값 매개변수 방식은 짧지만 호출하는 쪽에서 f(10)처럼 값을 덮어쓸 수 있고, 팩토리 함수 방식은 조금 길지만 의도가 분명하다는 차이가 있습니다.
- Tip : 같은 문제는
for반복문 안에서def로 함수를 정의할 때도 똑같이 발생합니다.lambda만의 문제가 아니라 클로저의 동작 방식입니다.
클로저와 데코레이터
클로저는 함수를 인수로 받아 기능을 덧붙인 새 함수를 반환하는 데에도 사용됩니다. 이 형태가 제 47강에서 다룰 데코레이터(Decorator)의 기본 구조입니다.
def logging(func):
def wrapper(*args):
print(f"{func.__name__}{args} 호출")
return func(*args)
return wrapper
def add(x, y):
return x + y
add = logging(add)
print(add(2, 3))
print(add.__code__.co_freevars)- 결과
- add(2, 3) 호출
5
(‘func’,)
logging(add)는 원래의 add 함수를 자유 변수 func로 기억하는 클로저 wrapper를 반환합니다.
반환된 wrapper를 다시 add라는 이름에 대입했으므로, 이후 add(2, 3)을 호출하면 wrapper가 실행되어 먼저 호출 정보를 출력한 다음 셀에 보관된 원래 함수를 호출해 5를 반환합니다.
마지막 줄의 ('func',)로 원래 함수가 자유 변수로 저장되어 있음을 확인할 수 있습니다.
정리
| 이름 | 설명 | 반환값/특징 |
|---|---|---|
| 클로저 | 바깥 함수의 변수를 참조하는 내부 함수 | 바깥 함수가 끝나도 변수(셀)를 유지 |
| 자유 변수 | 내부 함수에서 사용하지만 그 안에서 정의하지 않은 바깥 함수의 변수 | __code__.co_freevars로 이름 확인 |
| 셀(cell) | 자유 변수를 담는 객체 | cell_contents로 현재 값 확인 |
__closure__ |
함수가 가진 셀의 튜플 | 자유 변수가 없으면 None |
| nonlocal | 바깥 함수의 변수에 대입하도록 선언 | 바깥 변수가 없으면 SyntaxError |
| 늦은 바인딩 | 자유 변수의 값은 호출 시점에 조회 | 기본값 매개변수나 팩토리 함수로 고정 |
- 바깥 함수를 호출할 때마다 새로운 셀이 만들어지므로, 같은 팩토리로 만든 클로저들은 서로 독립된 상태를 가집니다.
- 바깥 변수를 읽기만 하거나 리스트처럼 내용만 변경할 때는 선언이 필요 없고, 새 값을 대입할 때는
nonlocal이 필요합니다. - 반복문에서 클로저를 여러 개 만들 때는 늦은 바인딩에 주의합니다.
댓글 남기기