하루 지나 풀면 더 오래 남습니다. 1~8단원을 마친 다음 날 풀어 보세요. 1~8단원에서 골고루 12문제를 섞었고, 어느 레슨의 문제인지는 정답을 펼쳐야 보입니다. 출력을 쓰는 문제는 빈칸과 줄바꿈까지 종이에 끝까지 쓴 뒤 정답을 펼치세요. 리스트와 딕셔너리는 파이썬이 찍는 꼴 그대로(쉼표와 콜론 뒤 빈칸 · 작은따옴표) 씁니다. 줄 수를 알려 주지 않는 문제는 마지막 줄까지 다 씁니다. 막히면 정답 앞의 힌트를 먼저 펼칩니다. 점수는 매기지 않습니다.
한 번에 다 풀 필요는 없습니다. 열두 문제 모두 폰으로도 풀 수 있습니다(약 30~40분).
6번과 11번은 긴 코드입니다. 추적 표(줄 · 바뀐 이름 · 값 · 출력)를 그리며 따라가세요. 함수를 부르는 문제는 함수 칸마다 이름이 어느 값에 붙는지, 리스트에 이름이 둘 붙는 문제는 두 이름이 어느 리스트(①, ②)에 붙는지 이름표를 그리며 따라가면 덜 틀립니다. 실행 결과는 모두 파이썬 3.15로 돌린 것입니다.
문제 1
세 배를 돌려주는 함수에 입력받은 값을 넘깁니다. 2를 넣었습니다. 무엇이 찍힐까요? 넣은 값이 보이는 줄까지 세 줄을 글자 그대로 쓰세요.
def triple(x):
return x * 3
n = input('수: ')
print(triple(n))
print(triple(int(n)))넣는 값2
힌트
5번 줄에서 n이 어떤 종류의 값에 붙는지 먼저 정하세요.
정답
수: 2
222
6- 5번 줄: 친 2로 글자 '2'가 만들어지고, n이 그 값에 붙습니다.
- 6번 줄: triple 칸에서 x가 글자 '2'에 붙습니다. 글자에
* 3을 쓰면 세 번 되풀이해 '222'를 돌려줍니다. - 7번 줄:
int(n)이 만든 수 2를 넘겨,2 * 3인 6을 돌려받아 찍습니다.
흔한 오답은 둘째 줄의 6입니다. 입력받은 2가 수로 들어온다고 본 것입니다(문자열과 수를 가리지 않는 생각). input()은 늘 글자를 돌려주고, 글자에 *를 쓰면 곱하지 않고 되풀이합니다.
다시 볼 레슨: 입력은 늘 글자 · 문자열 잇기와 되풀이 · 매개변수와 인자
문제 2
무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
a = [1, 2]
b = a
b.append([3, 4])
a.extend([5])
print(a)
print(len(b))
힌트
줄마다 이름표를 그려 a와 b가 가리키는 리스트가 몇 개인지 보고, 3번 줄과 4번 줄이 각각 몇 칸을 더하는지 세어 보세요.
정답
[1, 2, [3, 4], 5]
4| 줄 | 리스트 ① — a와 b가 함께 가리킴 |
|---|---|
| 1·2 | [1, 2] |
| 3 | [1, 2, [3, 4]] |
| 4 | [1, 2, [3, 4], 5] |
2번 줄 b = a는 새 리스트를 만들지 않고, a가 가리키는 리스트 ①에 이름 b를 하나 더 붙입니다(규칙 3). 3번 줄 append()는 받은 리스트 [3, 4]를 통째로 한 칸에 더하고, 4번 줄 extend()는 받은 리스트에서 값을 하나씩 꺼내 5를 더합니다. ①은 네 칸이라 len(b)는 4입니다.
- 첫 줄을
[1, 2, 5], 둘째 줄을3으로 쓰면 b가 사본을 받았다고 본 것입니다(복사된다는 생각).b = a는 같은 리스트에 이름을 하나 더 붙일 뿐입니다. [1, 2, 3, 4, 5]와5로 쓰면append()도 값을 하나씩 꺼내 더한다고 본 것입니다. 하나씩 꺼내 더하는 것은extend()이고,append()는 받은 값 하나를 한 칸에 더합니다.
다시 볼 레슨: 리스트와 이름 둘 · 리스트를 제자리에서 바꾸기
문제 3
앞 문제처럼 a와 b가 한 리스트에서 시작하는데, 이번에는 b를 다른 꼴로 늘립니다. 무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
a = [1, 2]
b = a
b = b + [3]
b.append(4)
print(a)
print(b)
힌트
3번 줄의 b + [3]이 무엇을 만드는지, 그 뒤 b가 어느 리스트에 붙는지 그려 보세요.
정답
[1, 2]
[1, 2, 3, 4]- 2번 줄: b가 a와 같은 리스트 ①에 붙습니다.
- 3번 줄:
b + [3]은 두 리스트를 이은 새 리스트 ②[1, 2, 3]을 만들고, b만 ②에 다시 붙입니다. a는 ①에 그대로 붙어 있습니다. - 4번 줄: b가 가리키는 ②의 끝에 4를 더합니다. ①은 그대로입니다.
흔한 오답은 두 줄 모두 [1, 2, 3, 4]로 쓰는 것입니다. b를 새 리스트에 다시 붙이면 a도 따라 옮겨 간다고 본 것입니다(따라 옮겨 간다는 생각). 다시 붙이기는 그 이름 하나만 옮기고 리스트를 바꾸지 않습니다(규칙 3). 앞 문제의 append()·extend()처럼 리스트를 제자리에서 바꿀 때만 다른 이름으로도 바뀐 것이 보입니다.
다시 볼 레슨: 리스트와 이름 둘 · 이름표 다시 붙이기
문제 4
주문이 끝나면 벨을 울리는 함수입니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
def ring():
print('딩동')
ring
print('주문 완료')
ring()
힌트
5번 줄과 7번 줄에서 ring 뒤에 무엇이 있는지 견주어 보세요.
정답
모두 2줄입니다.
주문 완료
딩동- 5번 줄: 이름 ring이 가리키는 함수를 꺼내기만 하고 부르지 않아, 아무 일도 일어나지 않습니다.
- 6번 줄:
주문 완료를 찍습니다. - 7번 줄: 괄호를 붙여 부르니 새 칸이 생기고 2번 줄이
딩동을 찍습니다.
흔한 오답은 맨 위에 딩동이 한 줄 더 붙은 세 줄입니다. 괄호 없이 이름만 써도 함수가 실행된다고 본 것입니다(괄호 없이도 부른다는 생각). 함수를 부르는 것은 이름 뒤의 괄호입니다(규칙 5).
다시 볼 레슨: 함수 만들고 부르기
문제 5
주문을 받고 잔 수를 찍으려 합니다. 이 코드를 실행하면 어떻게 될까요? 알맞은 것을 고르세요.
print('주문을 받습니다')
serve(2)
def serve(n):
print(n, '잔')
- ①
주문을 받습니다를 찍은 뒤 2번 줄에서NameError로 멈춘다 - ②
주문을 받습니다를 찍은 뒤 6번 줄이2 잔을 찍고 끝난다 - ③
주문을 받습니다를 찍은 뒤 2번 줄만 건너뛰고 끝까지 간다
힌트
2번 줄을 실행하는 그때 이름 serve가 무엇에 붙어 있는지 보세요.
정답
① — 실행 결과는 이렇습니다.
주문을 받습니다Traceback (most recent call last): File "main.py", line 2, in <module> serve(2) ^^^^^NameError: name 'serve' is not defined위에서 아래로 한 줄씩 실행하므로(규칙 1), 2번 줄을 실행할 때는 5번 줄의 def가 아직 함수를 만들어 이름 serve를 붙이지 않았습니다. 그래서 serve라는 이름이 없다는 NameError로 멈춥니다. 1번 줄은 이미 실행되어 주문을 받습니다는 남습니다.
- ② 파이썬이
def를 어디에 두든 먼저 읽어 둔다고 봤습니다.def줄도 위에서 아래로 실행될 때 비로소 함수를 만들어 이름을 붙입니다. 부르는 줄을 함수 정의 아래로 옮기면2 잔까지 찍힙니다. - ③ 오류가 난 줄만 건너뛴다고 봤습니다. 실행은 오류가 난 그 줄에서 멈춥니다(규칙 1).
다시 볼 레슨: 함수 만들고 부르기 · 오류 메시지 읽기
문제 6
문구점은 4,000원어치마다 쿠폰을 한 장 줍니다. 손님 넷이 3,000원짜리 공책을 차례로 1권, 2권, 3권, 4권 샀습니다. 쿠폰을 두 장 이상 받은 손님과 모두 준 쿠폰 수를 찍습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
def coupons(won):
return won // 4000
total = 0
for books in range(1, 5):
got = coupons(books * 3000)
total = total + got
if got > 1:
print(books, got)
print('합계', total)
힌트
바퀴마다 books → coupons에 넘기는 값 → got → total → 9번 줄의 조건 차례로 추적 표에 적습니다.
정답
모두 3줄입니다.
3 2
4 3
합계 6바퀴마다 한 줄로 줄인 추적 표입니다. 실행되는 줄마다 한 줄씩 쓰면 함수 칸의 2번 줄까지 26줄입니다.
| books | 넘긴 값 | got | total | got > 1 | 출력 |
|---|---|---|---|---|---|
| 1 | 3000 | 0 | 0 | False | |
| 2 | 6000 | 1 | 1 | False | |
| 3 | 9000 | 2 | 3 | True | 3 2 |
| 4 | 12000 | 3 | 6 | True | 4 3 |
| 반복 뒤(11번 줄) | 6 | 합계 6 |
7번 줄은 books * 3000을 먼저 계산한 값 하나를 넘기고, coupons 칸에서 won이 그 값에 붙어 4,000으로 나눈 몫을 돌려줍니다. range(1, 5)는 1, 2, 3, 4를 내줘 네 바퀴입니다. 10번 줄은 8칸이라 got > 1이 참일 때만 찍고, 11번 줄은 들여 쓰지 않아 반복이 끝난 뒤 한 번 찍습니다(규칙 4).
흔한 오답은 5 3과 합계 9까지 쓰는 것입니다. range(1, 5)에 끝 수 5도 든다고 본 것입니다(끝도 든다는 생각). 바퀴마다 1 0부터 4 3까지 네 줄을 찍는다고 쓰면 10번 줄이 if 덩어리 안이라는 것을 놓친 것입니다(들여쓰기는 꾸밈이라는 생각). 줄이 드는 덩어리는 들여쓰기 칸 수가 정합니다(규칙 4).
다시 볼 레슨: for와 range · 돌려받은 값 쓰기 · if와 else · 사칙 계산과 몫·나머지
문제 7
무엇이 찍힐까요? 한 줄을 글자 그대로 쓰세요.
orders = ['커피', '라테']
count = len(orders)
orders.append('녹차')
print(count, len(orders))
힌트
2번 줄이 실행되는 그때 orders가 가리키는 리스트가 몇 칸인지 보세요.
정답
2 3- 2번 줄: 그때 리스트가 두 칸이라
len(orders)가 2로 계산되고, count가 2에 붙습니다. - 3번 줄: 리스트 끝에 '녹차'를 더해 세 칸이 됩니다. count는 다시 계산되지 않습니다.
- 4번 줄: count는 2이고, 지금 다시 센
len(orders)는 3입니다.
흔한 오답은 3 3입니다. count가 len(orders)라는 식을 저장해 두었다가 찍을 때 다시 센다고 본 것입니다(식이 저장된다는 생각). =는 오른쪽을 그때 한 번 계산해 만든 값 하나에 이름을 붙입니다(규칙 2).
다시 볼 레슨: 변수: 값에 이름 붙이기 · 리스트와 이름 둘
문제 8
주문 번호를 알리는 함수 둘입니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
def call(no):
print(f'{no}번 나왔어요')
def ticket(no):
return f'{no}번 나왔어요'
print(ticket(7))
print(call(8))
힌트
9번 줄과 10번 줄의 바깥 print가 각각 무엇을 넘겨받는지, 함수가 돌려준 값을 먼저 적어 보세요.
정답
모두 3줄입니다.
7번 나왔어요
8번 나왔어요
None- 9번 줄: ticket 칸에서 no가 7에 붙고, 6번 줄이 글자 '7번 나왔어요'를 돌려줍니다. 바깥
print가 그 값을 찍습니다. - 10번 줄: call 칸에서 2번 줄이
8번 나왔어요를 화면에 찍고,return이 없는 call은None을 돌려줍니다. 바깥print가 그None을 찍습니다.
흔한 오답은 None을 빼고 두 줄만 쓰는 것입니다. call이 화면에 찍은 글자가 부른 자리로 돌아온다고 본 것입니다(찍는 것과 돌려주는 것이 같다는 생각). print는 화면에 쓰고 None을 돌려주며, 부른 자리로 값을 보내는 것은 return입니다(규칙 5).
다시 볼 레슨: 함수 만들고 부르기 · 매개변수와 인자
문제 9
택배 무게(kg)로 상자 크기를 정하는 함수입니다. 무엇이 찍힐까요? 한 줄을 글자 그대로 쓰세요.
def box(kg):
if kg >= 3:
return '대'
if kg >= 2:
return '중'
return '소'
print(box(3), box(2), box(1))
힌트
box(3)을 부를 때 3번 줄 다음에 실행되는 줄이 어디인지 보세요.
정답
대 중 소| 부르기 | kg >= 3 | kg >= 2 | 돌려준 값 |
|---|---|---|---|
| box(3) | True | (보지 않음) | '대' |
| box(2) | False | True | '중' |
| box(1) | False | False | '소' |
return을 만나면 값을 돌려주며 함수가 그 자리에서 끝납니다. 그래서 box(3)은 3번 줄에서 끝나 4번 줄의 if를 보지 않고, box(2)는 5번 줄에서 끝납니다(규칙 5).
흔한 오답은 소 소 소입니다. return 뒤의 줄도 실행되어 마지막 6번 줄의 값이 돌아간다고 본 것입니다. return은 값을 돌려주며 함수를 끝내므로 그 뒤의 줄로 가지 않습니다(규칙 5).
문제 10
가장 싼 값을 돌려주는 함수 둘입니다. 무엇이 찍힐까요? 두 줄을 글자 그대로 쓰세요.
def low_new(prices):
return sorted(prices)[0]
def low_sort(prices):
prices.sort()
return prices[0]
menu = [4500, 3500, 4000]
print(low_new(menu), menu)
print(low_sort(menu), menu)
힌트
함수를 부를 때마다 prices가 어느 리스트에 붙는지 그리고, 2번 줄과 6번 줄이 그 리스트를 바꾸는지 보세요.
정답
3500 [4500, 3500, 4000]
3500 [3500, 4000, 4500]- 11번 줄: low_new 칸에서 prices가 menu와 같은 리스트 ①에 붙습니다(규칙 3). 2번 줄
sorted(prices)는 정렬한 새 리스트 ②를 만들고, 그 0번 칸 3500을 돌려줍니다. ①은 그대로입니다. - 12번 줄: low_sort 칸에서 prices가 다시 ①에 붙고, 6번 줄
.sort()가 ①을 제자리에서 정렬합니다. 그래서 menu로 찍어도 정렬된 차례입니다.
흔한 오답은 둘째 줄의 3500 [4500, 3500, 4000]입니다. 함수가 넘겨받은 리스트의 사본을 정렬한다고 본 것입니다(복사된다는 생각). 리스트를 넘기면 매개변수가 같은 리스트를 가리켜, .sort()가 바꾼 차례가 밖에서도 보입니다(규칙 3). 첫 줄의 리스트도 정렬된 차례로 쓰면 sorted()도 처음 리스트를 바꾼다고 본 것입니다. sorted()는 정렬한 새 리스트를 돌려줄 뿐입니다.
다시 볼 레슨: 리스트를 함수에 넘기면 · 정렬: sort와 sorted
문제 11
오늘 주문을 첫 글자로 적었습니다(라: 라테 · 커: 커피 · 취: 취소). 취소는 건너뛰고 메뉴마다 잔 수를 셉니다. 무엇이 찍힐까요? 한 줄을 글자 그대로 쓰세요.
orders = '라라취커'
sold = {}
i = 0
while i < len(orders):
ch = orders[i]
i = i + 1
if ch == '취':
continue
before = sold.get(ch, 0)
sold[ch] = before + 1
print(sold)
힌트
바퀴마다 4번 줄의 조건 → ch → i → 7번 줄의 조건 → before → sold 차례로 추적 표에 적습니다.
정답
{'라': 2, '커': 1}바퀴마다 한 줄로 줄인 추적 표입니다. 실행되는 줄마다 한 줄씩 쓰면 28줄입니다.
| 바퀴 | i < len(orders) | ch | i | ch == '취' | before | sold |
|---|---|---|---|---|---|---|
| 1 | 0 < 4 → True | '라' | 1 | False | 0 | {'라': 1} |
| 2 | 1 < 4 → True | '라' | 2 | False | 1 | {'라': 2} |
| 3 | 2 < 4 → True | '취' | 3 | True → continue | 그대로 | |
| 4 | 3 < 4 → True | '커' | 4 | False | 0 | {'라': 2, '커': 1} |
| — | 4 < 4 → False | 반복 끝 |
셋째 바퀴의 continue는 그 바퀴의 남은 9·10번 줄만 건너뛰고 4번 줄로 돌아갑니다. i는 6번 줄에서 이미 3에 붙어 있어, 다음 바퀴는 넷째 글자부터 봅니다. 9번 줄의 get()은 키가 있으면 그 키의 값을, 처음 보는 키면 기본값 0을 돌려주고, 10번 줄이 1을 더한 값을 그 키와 한 쌍으로 담습니다.
흔한 오답은 {'라': 2}입니다. continue가 반복을 끝낸다고 본 것입니다. continue는 이번 바퀴의 남은 줄만 건너뜁니다. {'라': 1, '커': 1}로 쓰면 get()이 늘 기본값 0을 돌려준다고 본 것입니다. 기본값은 키가 없을 때만 돌아옵니다. 딕셔너리는 콜론과 쉼표 뒤에 빈칸을 넣고 키를 작은따옴표로 감싼 꼴로 찍힙니다.
다시 볼 레슨: break와 continue · while과 추적 표 · 세기: 메뉴별로 모으기
문제 12
인형 안에 작은 인형이 든 인형을 열고 닫습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
def doll(n):
if n > 0:
print('열기', n)
doll(n - 1)
print('닫기', n)
doll(2)
힌트
부를 때마다 칸을 하나씩 쌓아 그리고 칸마다 n을 적은 뒤, doll(0) 칸이 끝나면 실행이 어느 칸의 몇 번 줄로 가는지 보세요.
정답
모두 4줄입니다.
열기 2
열기 1
닫기 1
닫기 2칸이 쌓이고 사라지는 차례대로 적은 표입니다.
| 차례 | 칸 | 그 칸의 n | 한 일 | 출력 |
|---|---|---|---|---|
| 1 | doll(2) | 2 | 3번 줄, 4번 줄에서 doll(1) 부름 | 열기 2 |
| 2 | doll(1) | 1 | 3번 줄, 4번 줄에서 doll(0) 부름 | 열기 1 |
| 3 | doll(0) | 0 | n > 0이 거짓이라 바로 끝남 | |
| 4 | doll(1) | 1 | 돌아와 5번 줄 | 닫기 1 |
| 5 | doll(2) | 2 | 돌아와 5번 줄 | 닫기 2 |
부를 때마다 새 칸이 쌓이고, 칸마다 n이 따로 붙습니다. doll(0)이 끝나면 자기를 부른 doll(1) 칸으로 돌아와 4번 줄 다음인 5번 줄을 실행하고, doll(1)이 끝나면 doll(2) 칸으로 돌아옵니다(규칙 5). 문제 11의 while은 한 칸에서 이름 i를 다시 붙여 가며 되풀이했지만, 재귀는 칸이 쌓였다가 하나씩 사라지며 돌아온 칸마다 자기 n으로 남은 줄을 실행합니다.
닫기 0을 두 번 쓰면 n이 이름 하나라 0까지 줄어든다고 본 것입니다. 칸마다 n이 따로 있어, 돌아온 칸은 자기 칸의 n을 찍습니다(규칙 5).열기 2와열기 1두 줄만 쓰면 끝 조건에서 모두 끝난다고 본 것입니다. 끝난 칸은 자기를 부른 칸으로 돌아가 남은 줄을 실행합니다.닫기 2를닫기 1보다 먼저 쓰면 돌아오는 차례가 부르는 차례와 같다고 본 것입니다. 가장 나중에 쌓인 칸이 가장 먼저 끝나고 돌아옵니다.
다시 볼 레슨: 재귀: 자기를 부르는 함수