함수 안에서 전역 변수를 더했더니 UnboundLocalError가 난 이유
문제 발생
카운터를 올리는 함수에서 예외가 났습니다. 읽기만 하던 코드는 잘 됐는데, 더하기를 넣자마자 깨졌습니다.
count = 0
def show():
print(count) # 잘 동작한다
def bump():
count += 1 # UnboundLocalError: cannot access local variable 'count'
# where it is not associated with a value원인 분석
파이썬은 함수를 컴파일할 때 그 함수 안에서 이름에 대입이 일어나는지를 먼저 봅니다. 대입이 하나라도 있으면 그 이름은 함수 전체에서 지역 변수가 됩니다 — 대입이 나오는 줄부터가 아니라 함수 시작부터입니다.
count += 1은 "읽어서 더하고 다시 대입"이므로 대입입니다. 그래서 count는 지역 변수가 되고, 오른쪽에서 읽는 시점에는 아직 값이 없습니다.
문서의 정의 그대로입니다 — UnboundLocalError는 함수나 메서드 안에서 지역 변수를 참조했는데 그 변수에 값이 바인딩되지 않았을 때 발생하며, NameError의 하위 클래스입니다. NameError가 "지역이든 전역이든 이름을 찾을 수 없음"이라는 넓은 범주이고, UnboundLocalError는 그중 지역 변수에 한정된 경우입니다.
show()가 동작한 이유는 대입이 없어서입니다. 대입이 없으면 그 이름은 지역이 아니고, 전역에서 찾습니다.
해결 방안
- 가장 좋은 해결은 전역 변수를 쓰지 않는 것입니다. 값을 받아서 새 값을 돌려주면 이 문제 자체가 없어지고 테스트도 쉬워집니다.
def bump(count):
return count + 1
count = bump(count)- 정말 전역을 바꿔야 하면
global을 선언합니다. 다만 이 선언 자체가 "이 함수는 바깥 상태를 바꾼다"는 경고 표시입니다.
def bump():
global count
count += 1- 중첩 함수에서 바깥 함수의 변수를 바꾸려면
nonlocal입니다.global과 자리가 다릅니다.
def counter():
count = 0
def bump():
nonlocal count
count += 1
...- 조건부 대입에서도 같은 함정이 있습니다. 어느 분기에서도 대입이 없으면 그 경로에서 터집니다.
def label(score):
if score > 90:
text = "우수"
return text # score가 90 이하면 UnboundLocalError먼저 기본값을 두면 없어집니다.
def label(score):
text = "보통"
if score > 90:
text = "우수"
return text- 가변 객체는 대입이 아니라 변경으로 다룹니다. 리스트에
append하는 것은 대입이 아니므로global없이도 동작합니다 — 다만 이 역시 바깥 상태를 바꾸는 것이라 신중해야 합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.