요약
파이썬에서 스코프(scope)는 특정 변수를 정의하고 접근할 수 있는 프로그램의 영역이다. 프로그램 내 변수의 가시성(visibility) 또는 접근성을 결정한다.
파이썬에는 네 가지 유형의 스코프가 있다.
- Local scope
가장 내부의 scope로, 함수가 호출될 때마다 만들어진다. 함수 내에서 정의된 변수는 해당 함수 내에서만 사용할 수 있다. - Enclosing scope (nonlocal scope)
함수가 다른 함수 내에서 정의될 때 만들어지는 스코프이다. enclosing 함수에서 정의된 변수는 내부 함수에서도 접근할 수 있다. - Global scope
코드 전체에 해당하는 스코프이다. 코드의 최상위 수준에서 정의된 변수는 전역 scope에 있다. 이러한 변수는 프로그램 내의 모든 함수나 클래스에서 접근할 수 있다. - Built-in scope
Python에서 기본적으로 사용 가능한 내장 함수와 모듈의 스코프이다.print()
,len()
,math.sqrt()
등이 여기에 해당된다. 이러한 변수는 프로그램의 모든 모듈이나 함수에서 접근할 수 있다.
변수가 프로그램에서 참조될 때, 인터프리터는 변수를 local scope, enclosing scope, global scope, built-in scope 순서로 확인한다. 변수가 이러한 scope들에서 찾을 수 없으면 NameError
가 발생한다.
scope의 구분
a = 1
def outer(c):
b = 2
def inner():
c = 3
return b * c
return inner()
print(outer(10))
위와 같은 코드가 있다고 하자. 실행을 시켜보면 9
가 출력 된다. 위의 함수는 c
값에 대한 코드가 두 번 제시된다. 먼저 마지막 줄에서 outer(10)
을 통해 한 번, inner()
내부에서 초기화 하는 역할로 한 번. 이 코드의 결과가 결국 9
라는 것은 마지막 줄에서 호출 시 전달된 10이 우선순위에서 밀렸다는 뜻이 된다. 이 사태를 scope를 통해 확인해 보자.
inner()
를 기준으로 먼저 살펴보면, inner()
내부는 local scope이다. 따라서 c
는 local scope에 있다. 외부에서 전달된 10은 local scope의 변수가 우선순위가 높아 초기화가 되어 3으로 결정 된다. b
는 enclosing scope로 inner()
에서 사용할 수 있다. 따라서 return inner()
의 값이 9
가 된다.
outer()
를 기준으로 본다면 b
와 c
모두 local scope라고 할 수 있다.
a
는 outer() 밖에 있으므로 global scope에 있는 변수이다. 당연히 outer()와 inner()에서 사용할 수 있다.
nonlocal scope
nonlocal scope와 enclosing scope는 비슷한 개념이지만 약간 다르다. enclosing scope는 현재의 범위 밖에 있는 모든 범위를 포괄하는 더 넓은 개념인 반면, nonlocal scope는 구체적으로 중첩된 함수의 바로 밖에 있는 범위를 가리킨다.
다음 코드를 살펴보자.
def outer():
def inner():
print(c) # 에러 발생
c = 2
return
c = 1
inner()
print(c)
return
outer()
위 코드를 실행하면 다음과 같은 에러가 나온다.UnboundLocalError: local variable 'c' referenced before assignment
에러의 원인은 inner()
에서 c
가 선언되지 않았는데 print(c)
로 호출해서 그렇다. 대충 보면 inner()
가 호출되기 전에 outer()
에서 c = 1
로 c
를 선언해 준 것 같지만 사정이 조금 복잡하다.
inner()
는 print(c)
를 통해 c
를 출력하고자 하는데 인터프리터는 c
를 일단 inner()
의 local 변수라고 생각한다. 그런데 현재 c
는 1인데 inner()
의 c
가 outer()
의 c
를 새로 쓴다(write)고 판단해 에러 메시지를 띄운다. local 영역에서 밖의 영역에 대한 값을 참조, 또는 읽는 것은 상관이 없는데, 값을 수정하거나 새로 할당하는 것 즉, 쓰는 것은 안 된다.
이는 예상치 못한 버그를 방지하기 위함이다. 변수가 지금보다 많고 다른 기능도 있다면 외부의 변수를 함부로 건드릴 때 어떻게 변할지 예상이 힘든 상황이 펼쳐질 수도 있기 때문이다.
그렇다면 어떻게 문제를 해결해야 하는가? nonlocal
statement를 사용하면 된다.
def outer():
def inner():
nonlocal c # c는 이제 local이 아닌 nonlocal 변수가 된다
print(c)
c = 2
return
c = 1
inner()
print(c)
return
outer()
이제 c
는 outer()
scope의 변수가 되어 outer()
내부에서는 어디서나 읽고 쓸 수 있게 되었다.
코드를 실행 해 보면 처음에 c = 1
이어서 inner()
에 의해 1
이 출력 되고, 그 후 마지막 print(c)
는 inner()
에서 c
가 2가 되었기 때문에 2
를 출력한다.
def outer():
def inner():
print(c)
return
c = 1
inner()
print(c)
return
outer()
위와 같이 inner()
의 c = 2
를 제거하면 1
이 출력 된다. 이제는 c
가 enclosing 변수이기 때문이다.