이 때 클래스나 함수를 정의할 때 내부에 사용된 변수의 이름은 다음 룰이 적용된다.
- 선언이나 정의되지 않은 이름을 사용하는 경우에는 해당 단위의 이름공간에 그 이름이 없는 경우, 상위 이름 공간을 참조한다.
- 이름 바인딩은 함수가 실행되는 시점이 아닌 해석되는 시점에 온다.
1은 변수 shadowing에 대한 내용이다. 만약 특정 스코프에서 정의되지 않은 이름을 참조하는 경우, 상위 스코프에서 해당 이름을 찾게 된다. 이는 많은 언어들이 지원하는 기능인데, 파이썬의 경우 이 범위가 모든 블럭이 아닌, 자체적으로 이름 공간을 갖는 함수, 클래스, 모듈 단위로 구성되어 있다는 점이 차이가 있다.
문제는 2번 규칙이다. 함수 내부의 이름 공간은 함수가 실행되는 시점이 아닌, 함수가 처음 해석되는 시점에 바인딩이 이루어진다. 다음 코드를 보자.
x = 1
def func_a():
print(x) # 전역 변수 x를 출력하겠지?
x = 2
print(x) # 이번에는 지역변수 x를 출력하겠지?
print(x)
func_a()
print(x)
하지만 불행히도 이 코드는 UnboundLocalError
를 내며 실행이 되지 않는다. 왜냐면
- 함수를 호출하는 코드를 만나기 이전에 파이썬 인터프리터는 함수를 정의하는 코드를 먼저 만나게 된다.
- 함수의 내부에는
x = 2
라는 이름을 바인딩하는 코드가 있다. 따라서x
는 함수의 위치에 무관하게 함수의 이름 공간내에 바인딩이 만들어진다. - 그런데 함수를 호출하면 이름에 값이 바인딩되기 이전에
print(x)
를 통해 참조된다. 그래서 에러가 발생한다.
다시 아래와 같이 코드를 변경해주면 지역 변수로 동작하는 것을 볼 수 있다.
x = 1
def func_a():
x = 2
print(x) #여기서 x는 지역변수이다.
print(x)
func_a()
print(x)
x는 함수 내에서 새롭게 정의되었으므로 지역변수이다. 만약 함수 내에서 전역 변수 x를 쓰고자 한다면 global x
를 명시해야 한다.
x = 1
def func_a():
global x
x = 2
print(x)
print(x)
func_a()
print(x)
만약 다음과 같이 케이스는 어떨까?
b = 1
def func_x():
b = 2
print("b:", b)
def func_y():
b = 3
print("b at nested func:", b)
func_y()
print("b again:", b)
print("b at global:", b)
func_x()
print("b at global:", b)
함수 내에 다른 함수가 있고 전역 스코프 및 각 레벨의 함수에 대해서 각각 같은 이름의 변수가 정의되어 있다. 만약 func_y
에서 func_x
의 변수 b를 가져다가 변경하고 싶다면 어떻게 해야 할까? global
은 해당 변수가 전역이라고 선언하는 것이니 별도의 statement가 필요하다. 여기에 사용되는 것이 nonlocal
구문이다.
만약 func_y
에서 global
을 쓴다면?
b = 1
def func_x():
b = 2
print("b:", b)
def func_y():
global b
b = 3
print("b at nested func:", b)
func_y()
print("b again:", b)
print("b at global:", b)
func_x()
print("b at global:", b)
#b at global: 1
#b: 2
#b at nested func: 3
#b again: 2
#b at global: 3
전역 변수 b
를 건드리게 되는데, nonlocal
을 쓰게되면 바로 상위의 스코프의 변수를 바꿀 수 있게 된다.
b = 1
def func_x():
b = 2
print("b:", b)
def func_y():
nonlocal b
b = 3
print("b at nested func:", b)
func_y()
print("b again:", b)
print("b at global:", b)
func_x()
print("b at global:", b)
# b at global: 1
# b: 2
# b at nested func: 3
# b again: 3
# b at global: 1