반응형

바쁜 분은 이것만

  1. 함수 안에서 대입(=, +=)하는 순간 그 이름은 함수 전체에서 지역 변수가 된다
  2. 모듈 맨 위 변수를 바꾸려면 함수 첫 줄에 global count, 바깥 함수 변수면 nonlocal n
  3. 더 좋은 방법은 값을 인자로 받고 return으로 돌려주기

함수 밖에 만든 변수를 함수 안에서 1 올리려고 했을 뿐인데 이런 에러가 납니다. Python 3.14에서 실행한 그대로예요.

count = 0

def add():
    count += 1
    return count

print(add())
Traceback (most recent call last):
  File "~/work/u1.py", line 7, in <module>
    print(add())
          ~~~^^
  File "~/work/u1.py", line 4, in add
    count += 1
    ^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

같은 코드를 맥에 기본으로 깔린 Python 3.9.6으로 돌리면 메시지만 다르게 나옵니다. 검색할 때 둘 다 같은 에러라고 보면 돼요.

UnboundLocalError: local variable 'count' referenced before assignment

1. 왜 나는가: 대입하면 지역 변수가 된다

파이썬은 함수를 실행하기 전에 함수 본문을 훑어서, 함수 안에서 대입하는 이름은 모두 지역 변수로 정해 둡니다. count += 1은 count = count + 1이라서 대입이에요. 그래서 함수 안의 count는 바깥 count가 아니라 아직 값이 없는 지역 변수가 되고, 오른쪽의 count + 1을 계산하려는 순간 "값이 연결되지 않은 지역 변수"라는 에러가 납니다.

헷갈리는 건 대입이 읽는 줄보다 아래에 있어도 똑같다는 점입니다.

x = 10

def show():
    print(x)      # 바깥 x를 읽고 싶었는데
    x = 20        # 아래에서 대입하는 순간 x는 지역 변수가 됨

show()
  File "~/work/u2.py", line 4, in show
    print(x)      # 바깥 x를 읽고 싶었는데
          ^
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

4번째 줄의 print(x)에서 에러가 났지만 원인은 5번째 줄의 x = 20입니다. 이 줄을 지우고 실행하면 바깥 x인 10이 그대로 출력됐어요. 에러가 난 줄만 보지 말고 같은 함수 안에 그 이름에 대입하는 줄이 있는지 찾아보세요.

2. 해결 ① global: 모듈 맨 위 변수를 바꿀 때

count = 0

def add():
    global count
    count += 1
    return count

print(add(), add(), add())
print("count =", count)
1 2 3
count = 3

global count는 "이 함수 안의 count는 모듈 맨 위의 count"라고 알려 주는 선언입니다. 함수 첫머리에 적어 두는 게 보통이에요.

3. 해결 ② nonlocal: 함수 안의 함수에서 바깥 함수 변수를 바꿀 때

함수 안에 함수를 만든 경우(클로저)에도 같은 에러가 납니다.

def make_counter():
    n = 0
    def inc():
        n += 1
        return n
    return inc

c = make_counter()
c()
  File "~/work/u5.py", line 4, in inc
    n += 1
    ^
UnboundLocalError: cannot access local variable 'n' where it is not associated with a value

여기서 n은 모듈 맨 위가 아니라 make_counter 안에 있으니 global이 아니라 nonlocal을 씁니다.

def make_counter():
    n = 0
    def inc():
        nonlocal n
        n += 1
        return n
    return inc

c = make_counter()
print(c(), c(), c())
d = make_counter()
print(d())
1 2 3
1

make_counter()를 새로 부르면 n도 새로 생겨서, d는 다시 1부터 셉니다.

반대로 바깥 함수에 없는 이름에 nonlocal을 쓰면 실행 전에 바로 막힙니다.

x = 1
def f():
    nonlocal x
  File "~/work/u9.py", line 3
    nonlocal x
    ^^^^^^^^^^
SyntaxError: no binding for nonlocal 'x' found

모듈 맨 위 변수에는 global, 함수 안 변수에는 nonlocal입니다.

4. 리스트 append는 왜 global 없이 되나

items = []

def add_item(x):
    items.append(x)   # 대입이 아니라 메서드 호출 → global 필요 없음

add_item("a"); add_item("b")
print(items)

def reset():
    items = []        # 대입 → 지역 변수 새로 생김, 바깥 items는 그대로

reset()
print(items)
['a', 'b']
['a', 'b']

items.append(x)는 이름 items에 새 값을 대입하는 게 아니라, 바깥 리스트를 꺼내서 그 안을 바꾸는 것이라 에러가 나지 않아요. 반면 reset()의 items = []는 대입이라 함수 안에 새 지역 리스트가 생기고 끝납니다. 에러도 안 나고 바깥 리스트도 안 비워져서, 이쪽이 오히려 찾기 어려운 버그가 돼요. 비우고 싶다면 items.clear()를 쓰면 됩니다.

5. 같은 에러, 다른 원인: if 안에서만 대입한 경우

def check(flag):
    if flag:
        result = "ok"
    return result

print(check(True))
print(check(False))
ok
Traceback (most recent call last):
  ...
  File "~/work/u8.py", line 4, in check
    return result
           ^^^^^^
UnboundLocalError: cannot access local variable 'result' where it is not associated with a value

바깥 변수와는 상관없는 경우입니다. flag가 False면 result = "ok" 줄을 건너뛰어서 값이 없는 채로 return에 도착해요. if 위에 result = None처럼 기본값을 먼저 넣어 두면 됩니다. try 안에서만 변수를 만들고 except 뒤에서 쓰는 경우도 같은 이유로 납니다.

def load():
    try:
        data = int("abc")
    except ValueError:
        print("변환 실패")
    return data
load()
변환 실패
...
UnboundLocalError: cannot access local variable 'data' where it is not associated with a value

int("abc")가 실패해서 data에 값이 들어가지 못했어요. except 안에서 data = 0처럼 대신할 값을 넣거나, 그냥 return하면 됩니다.

6. global을 안 쓰는 방법

global은 짧은 스크립트에선 편하지만, 함수가 늘어나면 어디서 값이 바뀌는지 따라가기 어렵습니다. 값을 인자로 받고 돌려주거나, 상태가 여러 개면 클래스로 묶는 방법도 있어요.

def add(count):
    return count + 1

count = 0
count = add(count)
count = add(count)
print("count =", count)

class Counter:
    def __init__(self):
        self.n = 0
    def add(self):
        self.n += 1
        return self.n

c = Counter()
c.add(); c.add()
print("c.n =", c.n)
count = 2
c.n = 2

self.n += 1은 이름 n이 아니라 객체의 속성을 바꾸는 것이라 global·nonlocal이 필요 없습니다.

정리

상황 해결
함수 안에서 모듈 맨 위 변수에 += global 이름 또는 인자·return
안쪽 함수에서 바깥 함수 변수에 += nonlocal 이름
읽는 줄 아래에 같은 이름 대입이 있음 변수 이름을 다르게
if·try 안에서만 대입 위에서 기본값 먼저 넣기
리스트·딕셔너리 내용만 바꿈 에러 안 남 (append, clear, d[k] = v)

참고: Python 공식 문서 — Programming FAQ "Why am I getting an UnboundLocalError when the variable has a value?", Language Reference 7.12 The global statement, 7.13 The nonlocal statement

반응형

+ Recent posts