범위(scope)는 이름이 보이는 곳입니다. 함수 안에서 붙인 이름은 그 함수 칸에만 있다가 칸과 함께 사라지고, 함수 안에서 읽는 이름은 그 칸에 없으면 전역 칸에서 찾습니다.
함수 안과 밖에서 같은 이름을 쓰면, 함수 안에서 그 이름을 다른 값에 붙일 때 밖의 이름도 따라가리라 여기기 쉽습니다. 실제로는 이름이 칸마다 따로 있어서, 어느 칸의 이름인지 알아야 결과를 맞힐 수 있습니다. 이 레슨에서는 규칙 5를 넓혀, 함수 안에서 이름을 어디서 찾는지 세웁니다.
1함수 안에서 붙인 이름은 그 칸에만 있다
그림의 코드입니다.
def add():
count = 1
count = 0
add()
print(count)
05번 줄은 전역 칸에서 이름 count를 0에 붙입니다. 6번 줄이 add를 부르면 새 칸이 생기고, 2번 줄 count = 1은 그 함수 칸에 새 이름 count를 만들어 1에 붙입니다. 이름은 같아도 전역 칸의 count와는 다른 이름표입니다.
함수가 끝나면 칸이 사라지고, 칸 안의 count도 함께 사라집니다(규칙 5). 그래서 7번 줄은 전역 칸의 count가 붙은 0을 찍습니다.
함수 칸의 이름을 자료에 따라 지역 변수(local variable), 전역 칸의 이름을 전역 변수(global variable)라고 부릅니다.
함수 안에서만 붙인 이름을 밖에서 읽으면 그런 이름이 없다는 오류가 납니다.
def f():
t = 1
f()
print(t)
Traceback (most recent call last): File "main.py", line 6, in <module> print(t) ^NameError: name 't' is not definedt는 f의 칸에서만 1에 붙어 있다가 칸과 함께 사라졌습니다. 6번 줄이 찾는 전역 칸에는 t가 없습니다.
2읽는 이름은 함수 칸에 없으면 전역 칸에서 찾는다
함수가 바깥 이름을 읽기만 할 때도 있습니다.
def show():
print(count)
count = 0
show()
0show의 칸에는 count가 없습니다. 2번 줄이 count를 읽으면 파이썬은 먼저 함수 칸에서 찾고, 없으니 전역 칸에서 찾아 0을 찍습니다.
이름을 찾는 차례를 규칙 5에 더하면 이렇습니다. 함수 안에서 이름을 읽으면 그 함수 칸에서 찾고, 없으면 전역 칸에서 찾습니다. 전역 칸에도 없으면 마지막으로 print·len처럼 파이썬이 미리 둔 이름에서 찾습니다. 함수 몸 어디서든 그 이름을 값에 붙이는 줄이 있으면, 그 이름은 처음부터 함수 칸의 이름입니다(규칙 5).
그림의 add는 몸에 count = 1이 있어서 count가 처음부터 함수 칸의 이름이었습니다.
그러면 함수 안에서 바깥 count에 1을 더하려고 count += 1을 쓰면 어떻게 될까요? 무엇이 찍힐지, 오류가 난다면 무슨 오류일지 정한 뒤 펼쳐 보세요.
def add():
count += 1
count = 0
add()
print(count)
실행 결과 보기
Traceback (most recent call last): File "main.py", line 6, in <module> add() ~~~^^ File "main.py", line 2, in add count += 1 ^^^^^UnboundLocalError: cannot access local variable 'count' where it is not associated with a value예상과 다르면 그곳이 배울 곳입니다.
count += 1은 count를 읽어 1을 더한 값에 count를 다시 붙이는 줄입니다. 몸에 count를 붙이는 줄이 있으니 count는 처음부터 add 칸의 이름이고, 그 이름은 아직 어떤 값에도 붙지 않았습니다. 그래서 읽는 순간 오류가 납니다.
맨 아래 줄은 '함수 안 이름 count가 아직 값에 붙지 않았는데 읽으려 했다'는 뜻입니다. 오류 메시지는 부른 6번 줄부터 함수 안 2번 줄까지 차례로 적고, in add는 add 안이라는 뜻입니다.
1이 찍힌다고 예상했다면, 함수 안의 count가 전역 count와 같은 이름표라고 본 것입니다.
예제
넘기고 돌려받기
함수 안에서 바깥 값을 바꾸고 싶으면, 값을 인자로 넘기고 돌려받은 값에 바깥 이름을 다시 붙입니다.
def add(n):
return n + 1
count = 0
count = add(count)
print(count)
16번 줄은 전역 count가 붙은 0을 넘기고, 함수가 돌려준 1에 전역 count를 다시 붙입니다. 함수는 받은 값과 자기 칸의 이름만 쓰니, 어디서 무엇이 바뀌는지 6번 줄 하나로 보입니다.
함수 첫 줄에 global count를 쓰면 그 함수에서 count를 전역 칸의 이름으로 쓸 수도 있습니다. 그러면 바뀌는 곳을 찾기 어려워져 이 과목은 쓰지 않습니다.
자주 틀리는 곳
틀린 생각: 함수 안에서 붙인 이름도 밖의 같은 이름과 한 이름표다.
같은 글자로 쓴 이름이니 하나의 이름표처럼 보입니다. 들여 쓴 함수 몸은 그 함수의 덩어리이고, 그 안에서 붙인 이름은 함수 칸에 따로 생깁니다.
def f():
x = 10
x = 1
f()
print(x)
12번 줄의 x는 f의 칸에 따로 생겼다가 칸과 함께 사라졌습니다. 전역 x는 1 그대로입니다.
맞는 규칙: 함수 몸에서 붙인 이름은 그 함수 칸에만 있다가 칸과 함께 사라지고, 밖의 같은 이름은 그대로입니다. 바뀐 값이 밖에서 필요하면 return으로 돌려받습니다(규칙 5).
틀린 생각: count += 1은 바깥 count를 읽어 더하므로 함수 안에서도 바깥 count를 바꾼다.
읽기만 하는 함수는 바깥 count를 찾아 읽으니, 더하는 것도 될 것 같습니다. 그러나 붙이는 줄이 하나라도 있으면 그 이름은 처음부터 함수 칸의 이름입니다.
def add():
count += 1
count = 0
add()
Traceback (most recent call last): File "main.py", line 6, in <module> add() ~~~^^ File "main.py", line 2, in add count += 1 ^^^^^UnboundLocalError: cannot access local variable 'count' where it is not associated with a valueadd 칸의 count가 아직 값에 붙지 않았는데 2번 줄이 읽으려 해 멈췄습니다.
맞는 규칙: 함수 몸에 그 이름을 값에 붙이는 줄이 있으면 그 이름은 함수 칸의 이름이라, 붙기 전에 읽으면 오류가 납니다. 바깥 값을 바꾸려면 인자로 넘기고 return으로 돌려받습니다(규칙 5).
확인 문제
1. 그림 코드의 함수 몸에 print(count)를 한 줄 더했습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
def add():
count = 1
print(count)
count = 0
add()
print(count)
힌트
3번 줄과 8번 줄이 각각 어느 칸에서 count를 찾는지 봅니다.
정답
모두 2줄입니다.
1
03번 줄은 add 칸의 count를 찾아 1을 찍습니다. 함수가 끝나면 그 count는 칸과 함께 사라지고, 8번 줄은 전역 칸의 count가 붙은 0을 찍습니다.
흔한 오답은 1과 1입니다. 함수 안의 count가 전역 count와 한 이름표라고 본 것입니다(함수 안 이름이 밖과 같다는 생각). 2번 줄은 add 칸에 따로 이름을 만듭니다(규칙 5).
2. count에 1을 더하는 함수 add를 써서 count를 바꾸려 합니다. 아래 두 경우에 맞게 6번 줄의 빈칸을 채우세요.
- 5번 줄이
count = 5이면6이 찍힙니다. - 5번 줄을
count = 9로 바꾸면10이 찍힙니다.
def add(n):
return n + 1
count = 5
count = ____
print(count)
힌트
예제의 6번 줄처럼, 바깥 값을 넘기고 돌려받은 값에 count를 다시 붙입니다.
정답
add(count)입니다.
def add(n):
return n + 1
count = 5
count = add(count)
print(count)65번 줄을 count = 9로 바꾸면 10이 찍힙니다.
다르게 써도 되는 곳: count + 1도 6과 10을 찍지만 add를 쓰지 않습니다. add(5)나 6은 첫 경우만 맞고, count를 9로 바꿔도 따라 바뀌지 않아 6이 찍힙니다.
흔한 오답은 6번 줄에 add()만 쓰는 것입니다. 인자가 없어 n이 붙을 값이 없으므로 TypeError로 멈춥니다. n이 바깥 count의 값을 저절로 받는다고 본 것입니다. 함수에 넘어가는 것은 괄호 안의 값뿐입니다(규칙 5).
3. 시험 점수에 5점을 더하는 함수입니다. 무엇이 찍힐까요? 오류가 난다고 보면 찍히는 줄 다음에 오류 이름을 쓰세요.
def bonus():
score = score + 5
return score
score = 90
print(bonus())
힌트
2번 줄이 score를 읽을 때 score가 어느 칸의 이름인지, 그 칸에서 이미 값에 붙어 있는지 봅니다.
정답
아무것도 찍지 않고 UnboundLocalError로 멈춥니다.
Traceback (most recent call last): File "main.py", line 7, in <module> print(bonus()) ~~~~~^^ File "main.py", line 2, in bonus score = score + 5 ^^^^^UnboundLocalError: cannot access local variable 'score' where it is not associated with a value2번 줄은 score를 새 값에 붙이는 줄이라 score는 처음부터 bonus 칸의 이름입니다. 오른쪽 score + 5가 그 칸의 score를 읽는 순간 아직 값에 붙지 않아 멈춥니다.
흔한 오답은 95입니다. 함수 안의 score가 전역 score 90을 읽어 더한다고 본 것입니다. 붙이는 줄이 있으면 그 이름은 함수 칸에서만 찾습니다(규칙 5). 95를 찍으려면 def bonus(score):로 받아 print(bonus(score))로 넘깁니다.
5번 줄을 count = 5로 바꾸면 7번 줄이 무엇을 찍는지 봅니다.
처음 펼 때 약 6MB를 받습니다.컴퓨터에서 해 보기
파이썬 3.14. Tab을 누르면 다음 단추나 칸으로 갑니다. 들여쓰기는 빈칸 네 개입니다. Enter는 앞줄 들여쓰기를 잇고, 콜론 뒤에서는 한 단계 더 들여 씁니다. Ctrl+Enter로 실행합니다.
'실행'을 누르면 여기에 나옵니다.
여기서 쓴 코드 · 내 예상은 저장하지도 운영자에게 보내지도 않습니다. 페이지를 새로 열면 처음 코드입니다.