2.16. 스코프¶
Python이 함수 내부에서 이름을 조회할 때는 특정한 스코프 순서를 검색합니다. 그 순서를 이해하면 왜 어떤 할당은 바깥 이름을 가리고, 어떤 할당은 그것을 수정하며, 왜 중첩 함수가 정의된 곳의 값을 기억할 수 있는지 알 수 있습니다.
이름 조회는 로컬 함수 스코프에서 시작하여, 일치하는 것을 찾을 때까지 모듈 및 내장 스코프로 바깥쪽으로 진행됩니다.¶
2.16.1. 로컬 스코프와 모듈 스코프¶
함수 내부에서 정의된 이름은 그 함수에 로컬이며 호출이 끝나면 사라집니다:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
.py 파일의 최상위 수준에서 정의된 이름은 모듈 수준(때로 전역이라 불림)이며, 함수 내부를 포함하여 그 파일 전체에서 보입니다:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
함수 내부에서 모듈 수준 이름을 읽는 것은 자동입니다. 함수 내부에서 그 이름에 할당하는 것은 그렇지 않습니다. 그 할당은 새로운 로컬 변수를 생성하여 나머지 호출 동안 모듈 수준 이름을 가립니다:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
왼쪽의 counter는 counter를 bump 안의 로컬 이름으로 만들므로, 오른쪽의 읽기는 찾을 값이 없습니다.
2.16.1.1. global 키워드¶
함수 내부에서 실제로 모듈 수준 이름을 재할당하려면, 먼저 그것을 global로 선언하세요:
counter = 0
def bump():
global counter
counter += 1
global은 아껴서 사용하세요. 숨겨진 상태를 변경하는 함수는 값을 인수로 받아 새 값을 반환하는 함수보다 추론하기 어렵습니다. “상태를 공유해야 한다”에 대한 일반적인 해법은 객체(리스트, 딕셔너리, 클래스 인스턴스)를 인수로 전달하고 그것을 변경하는 것입니다.
2.16.2. 람다¶
lambda는 단일 표현식으로 작은 익명 함수를 만듭니다:
square = lambda x: x * x
square(7) # 49
이는 다음과 정확히 동등합니다
def square(x):
return x * x
lambda의 본문은 단일 표현식이어야 합니다. 문장도, 여러 줄도 안 됩니다. 주된 용도는 함수를 받는 무언가에 작은 함수를 인수로 전달하는 것입니다:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
본문이 하나의 표현식을 넘어 커지면, 실제 def로 전환하세요. def로 함수에 이름을 붙이면 트레이스백에서도 이름이 표시되는데, lambda는 이를 갖지 않습니다.
2.16.3. 클로저¶
다른 함수 내부에서 정의된 함수는 둘러싼 함수의 스코프에서 이름을 읽을 수 있습니다. 내부 함수는 그 이름들을 캡처하여, 바깥 호출이 반환된 후에도 계속 동작합니다:
def make_adder(n):
def add(x):
return x + n
return add
add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))
출력:
105 110
add5와 add10은 두 개의 별도 함수로, 각각 자신의 n을 기억합니다. 이런 식으로 맞춤형 내부 함수를 만들어 반환하는 함수를 클로저라고 합니다. 이것이 애초에 언어에 중첩 함수가 필요한 주된 이유입니다. 즉 어떤 상태를 함수 값에 구워 넣고 그 값을 단일 호출 가능 객체로 넘겨주는 방법입니다.
캡처된 이름을 읽는 것은 자동으로 일어납니다. 그중 하나를 재바인딩하려면 추가 키워드가 필요합니다. 아래 예제는 맞아 보이는 일을 하지만 실패합니다:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
tick 안에서 count에 대한 할당은, 최상위 함수에서 그것을 로컬로 만들었을 방식과 똑같이 count를 tick에 로컬로 만듭니다. nonlocal 키워드는 Python에게 “이 이름은 둘러싼 함수에 있으니, 거기서 재바인딩하라”고 알립니다:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
출력:
1 2 3
nonlocal이 둘러싼 함수 스코프에 대해 갖는 관계는 global이 모듈 스코프에 대해 갖는 관계와 같습니다. 캡처된 객체를 변경하는 것(some_list.append(...), some_dict[k] = v 호출)은 nonlocal이 필요하지 않다는 점에 유의하세요. 이름이 재바인딩되는 것이 아니라, 그것이 가리키는 객체만 변경되는 것이기 때문입니다.