본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev17

함수 안에서 전역 변수를 더했더니 UnboundLocalError가 난 이유

  • #Debugging
  • #Engineering Note
  • #Python

문제 발생

카운터를 올리는 함수에서 예외가 났습니다. 읽기만 하던 코드는 잘 됐는데, 더하기를 넣자마자 깨졌습니다.

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()가 동작한 이유는 대입이 없어서입니다. 대입이 없으면 그 이름은 지역이 아니고, 전역에서 찾습니다.

해결 방안

  1. 가장 좋은 해결은 전역 변수를 쓰지 않는 것입니다. 값을 받아서 새 값을 돌려주면 이 문제 자체가 없어지고 테스트도 쉬워집니다.
def bump(count):
    return count + 1

count = bump(count)
  1. 정말 전역을 바꿔야 하면 global을 선언합니다. 다만 이 선언 자체가 "이 함수는 바깥 상태를 바꾼다"는 경고 표시입니다.
def bump():
    global count
    count += 1
  1. 중첩 함수에서 바깥 함수의 변수를 바꾸려면 nonlocal입니다. global과 자리가 다릅니다.
def counter():
    count = 0
    def bump():
        nonlocal count
        count += 1
    ...
  1. 조건부 대입에서도 같은 함정이 있습니다. 어느 분기에서도 대입이 없으면 그 경로에서 터집니다.
def label(score):
    if score > 90:
        text = "우수"
    return text        # score가 90 이하면 UnboundLocalError

먼저 기본값을 두면 없어집니다.

def label(score):
    text = "보통"
    if score > 90:
        text = "우수"
    return text
  1. 가변 객체는 대입이 아니라 변경으로 다룹니다. 리스트에 append하는 것은 대입이 아니므로 global 없이도 동작합니다 — 다만 이 역시 바깥 상태를 바꾸는 것이라 신중해야 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.