Python — 슬라이싱 · 딕셔너리 · 집합 · 클래스 · 복사 E07

11회 전부에 나온 언어다. 첨자는 0부터, 끝은 빠지고, 음수는 뒤에서, 집합은 중복이 없고, 대입은 같은 것을 가리킨다

왜 이 장을 하나
Python 은 코드가 짧아 쉬워 보이지만 슬라이스의 끝이 빠진다는 것, 집합은 중복을 지우고 순서가 없다는 것, 얕은 복사는 안쪽을 공유한다는 것에서 매번 갈린다. 세 규칙과 문자열 · 딕셔너리 메서드 한 벌로 15문항이 풀린다.
다루는 것슬라이싱 · 문자열 메서드 · 리스트 · 딕셔너리 · 집합 · 컴프리헨션 · 클래스 · 복사
문항기출 15 · 예상 4 · 훈련 30
끝나면슬라이스를 보고 첨자 줄을 그려 읽는다

01왜 이 장인가

왜 이걸 하나 — 프로그래밍 91문항 중 Python 이 15문항이고 11회 전부에 나왔다. 2026년 1회에는 3문항이었다.

11회분 220문항 중 이 장의 기출은 15문항이다. 빠진 회차가 없고, 2026년 1회 3문항 · 2026년 2회 2문항으로 최근에 늘었다. 훈련 Python 30문항 전부가 이 장에 붙어 프로그래밍 여섯 장 중 훈련이 가장 많다.

몇 문항무엇을 적나보기
문자열 슬라이싱 · 메서드6슬라이스 조합 · count · split · 역순 · 글자 골라내기2023-2회 5번 · 2023-3회 16번 · 2024-1회 12번 · 2024-2회 8번 · 2026-1회 14번 · 2026-2회 5번
리스트 · 슬라이스 스텝3교환 후 짝수 · 홀수 첨자 합, 역순 스텝, 얕은 복사2024-3회 2번 · 2026-1회 10번 · 12번
딕셔너리 · 집합4집합 연산 결과 · 컴프리헨션 · 순회 · 합과 길이2023-1회 15번 · 2025-2회 17번 · 2025-3회 20번 · 2026-2회 13번
타입 · 클래스 · 재귀2type 비교 · 트리 홀수 레벨 합2024-3회 14번 · 2025-1회 17번

→ 슬라이스나 첨자로 글자를 고르는 문항이 15개 중 9개다. s[a:b] 는 a 부터 b 앞까지, s[::-2] 는 뒤에서 두 칸씩 — 첨자 줄을 그려 세는 습관 하나가 절반을 정한다.

E01 진단의 5번(_THIISING)과 13번(CNNLRPYT)이 이 장이고 둘 다 맞았다면 슬라이싱과 딕셔너리 순회는 됐다는 뜻이다. 남은 것은 집합의 중복 제거와 얕은 복사(2026년 1회 12번)처럼 실무에서 잘 안 밟는 함정이다.

문항 수는 이 노트의 문제 데이터에서 ch=E07 인 것을 센 값이다(2026-09-14). 기출 코드는 grandlife.co.kr 복원에서 옮겼고, 15문항 전부 Python 3.12 로 실행해 정답을 확인했다. 입력이 필요한 2026-1회 14번은 HumanDev 를 넣어 실행했다.

02한눈에

왜 이걸 하나 — 시험 전날 규칙 9개를 다시 본다.

슬라이스s[a:b] 는 첨자 a 부터 b 까지. s[a:] 끝까지, s[:b] 처음부터. s[::k] 는 k 칸씩, s[::-1] 역순, s[::-2] 뒤에서 두 칸씩. 음수 첨자 −1 은 마지막.
문자열 메서드len · count(x) · split()(공백 · 구분자로 나눠 리스트) · join · upper · lower · strip · replace · find. 문자열은 불변이라 s += "x" 는 새 문자열.
리스트append · pop()(마지막) · pop(0) · insert · sort()(제자리) · sorted()(새 리스트) · reversed. a, b = b, a 로 교환. range(a, b, k) 도 b 앞까지.
딕셔너리d[k] 조회 · 대입, d.get(k, 기본), d.keys() · values() · items(), for k, v in d.items(). 순회는 넣은 순서(3.7+). max(d, key=d.get) 은 값이 최대인 키.
집합중복 없음 · 순서 없음. add · remove · update. & 교집합 · | 합집합 · - 차집합. set(리스트) 로 중복 제거. 출력 순서는 묻지 않는 것이 보통.
컴프리헨션[x * 2 for x in a if x % 2]. 딕셔너리 {k: v for …}, 집합 {x for …}. 조건이 뒤에 있으면 거름, 앞에 있으면 삼항.
복사b = a 는 같은 객체. b = a[:] · list(a) 는 얕은 복사 — 겉은 새 리스트, 안의 리스트는 공유. b[i] += … 는 안쪽 리스트를 제자리에서 늘린다.
타입 · 클래스type(100) 은 int, type("") 은 str. bool 은 int 의 하위. 클래스는 __init__(self, …), 속성은 self.x, 클래스 변수는 모든 인스턴스가 공유. 상속 class B(A), super().f().
출력print(a, b) 는 공백으로 이어 붙임, end='' 면 줄바꿈 없음, sep 으로 구분자. 리스트를 찍으면 [1, 2] 꼴 그대로, 튜플은 (3, 6).

03밑바닥

왜 이걸 하나 — 규칙 9개는 첨자 줄 하나와 쪽지 그림 하나로 전부 그려진다.

3-1. 슬라이스는 「a 부터 b 앞까지」다

문자열 위에 첨자 줄을 0 1 2 … 로 적고, s[a:b] 는 a 에서 시작해 b 바로 앞에서 멈춘다. 2026년 2회 5번의 str[:4] 는 0~3 → _THI, str[6:8] 은 6~7 → IS, str[18:] 은 18부터 끝 → ING, 붙여서 _THIISING. 2023년 2회 5번(engneing)도 같은 규칙으로 조각 셋을 잇는다.

스텝이 붙으면 건너뛴다. lst[::-2] 는 뒤에서부터 두 칸씩이라 range(10) 에서 9 · 7 · 5 · 3 · 1 이고, 각각 뒤에 A 를 붙여 9A7A5A3A1A(2026년 1회 10번). 음수 스텝은 시작이 끝이고 끝이 시작이라는 것만 기억하면 된다.

글자 하나를 고르는 것은 첨자다. 2024년 1회 12번은 단어마다 word[1](둘째 글자)을 이어 붙여 Seynaau, 2026년 2회 13번은 키의 마지막 글자 key[-1] 과 값의 첫 글자 location[0] 을 번갈아 붙여 CNNLRPYT 다. −1 이 마지막이라는 것과 딕셔너리는 넣은 순서로 돈다는 것이 그 둘의 전부다.

3-2. 문자열 메서드는 새 값을 돌려주고 원본은 안 바뀐다

문자열은 불변이라 upper() · replace() · split() 은 새 값을 돌려주고 원본은 그대로다. 2024년 2회 8번은 text.count("3") 이 3, text.count("a") 이 3 이라 슬라이스 조각과 섞어 ab3ca3. 2026년 1회 14번은 입력 HumanDev 를 split() 하고 join 으로 붙인 뒤 [::-1] 로 뒤집고(veDnamuH) o · n · g 를 빼 veDamuH 다.

공백으로 두 값을 나눠 받는 관용구 a, b = input().split() 의 빈칸은 split 이다(2023년 3회 16번). 문자열 메서드는 「무엇을 돌려주나」(문자열인가 리스트인가 정수인가)를 알면 다음 줄이 읽힌다 — split 은 리스트, count 는 정수, join 은 문자열.

3-3. 집합은 중복을 지우고, 딕셔너리는 키로 찾는다

집합에 같은 것을 두 번 넣으면 하나만 남는다. 2023년 1회 15번은 add('중국') 이 이미 있어 아무 일도 없고, remove('일본') 으로 하나 빠진 뒤 update({'홍콩', '한국', '태국'}) 에서 한국이 겹쳐 최종 원소가 한국 · 중국 · 베트남 · 홍콩 · 태국 다섯이다. 문제가 「순서는 고려하지 않는다」고 한 것은 집합에 순서가 없기 때문이다.

2025년 2회 17번은 딕셔너리 컴프리헨션으로 {1: 2, 2: 4, 3: 6} 을 만들고 값의 집합 {2, 4, 6} 을 저장해 둔 뒤, doubled[2] = 7 로 값을 바꾸고 saved 에 99 를 넣는다. 교집합은 {2, 6} 이라 길이 2 — saved 는 만들 때의 값을 복사한 것이라 나중 변경이 안 따라오고, numbers 를 바꿔도 이미 만든 doubled 와는 무관하다는 것이 함정이다.

딕셔너리 순회는 items() 로 키와 값을 함께 꺼낸다. 2025년 3회 20번은 enumerate 로 안쪽 리스트마다 (합, 길이) 튜플을 만들어 {0: (15, 5), 1: (10, 3), 2: (18, 5), 3: (9, 2)} 를 찍고, 빈칸 여덟 개가 그 숫자들이다. 튜플 안의 순서(합이 먼저)를 코드에서 확인한다.

3-4. 대입은 같은 것을 가리키고, [:] 는 겉만 새것이다

b = a 는 같은 리스트를 두 이름으로 부르는 것이라 한쪽을 바꾸면 다른 쪽도 바뀐다. b = a[:] 는 새 리스트를 만들지만 안에 든 리스트 객체는 그대로 공유한다(얕은 복사). 2026년 1회 12번의 b = m[:]b[i + 1] += b[i] 는 안쪽 리스트를 제자리에서 늘리므로 m 의 안쪽도 함께 커져 길이 합이 1 + 2 + 3 + 4 = 10 이다.

리스트 교환 values[i], values[-i - 1] = values[-i - 1], values[i] 는 임시 변수 없이 자리를 바꾼다. 2024년 3회 2번은 앞뒤를 바꿔 6 5 4 3 2 1 을 만든 뒤 짝수 첨자 합(6 + 4 + 2)에서 홀수 첨자 합(5 + 3 + 1)을 빼 3 이다. 매 교환 뒤의 리스트를 다시 적는 것이 이 문제의 절차다.

타입 검사(2024년 3회 14번)는 type(value) == type(100) 이 int 인지, type("") 이 str 인지를 본다. "100.0" 은 문자열이라 길이 5, 100.0 은 float 이라 20, 튜플도 20 → 45. 클래스 문제(2025년 1회 17번)는 트리를 리스트로 만들고 홀수 레벨(1 · 3 …)의 값만 재귀로 더해 13 이다.

통과 기준은 넷이다. 첫째, 첨자 줄을 그려 s[a:b] · s[::-k] · s[-1] 을 30초 안에 읽는다. 둘째, 문자열 메서드가 무엇을 돌려주는지(문자열 · 리스트 · 정수) 말한다. 셋째, 집합의 중복 제거 · 무순서와 딕셔너리 순회 순서를 안다. 넷째, b = a · b = a[:] · 안쪽 공유의 차이를 그림으로 그린다. 기출 15문항이 이 넷 안에 있다.
s[2:5] 는 첨자 2 부터 5 까지 네 글자다.5 앞에서 멈춘다 — 2 · 3 · 4 세 글자. range(a, b) 도 같다. 2026년 2회 5번의 str[6:8] 이 두 글자 IS 인 이유다.
b = a[:] 로 복사했으니 b 를 바꿔도 a 는 안 바뀐다.겉 리스트만 새것이다. 안에 리스트가 들어 있으면 그 리스트는 공유라 b[i] += … 가 a 의 안쪽도 바꾼다. 2026년 1회 12번의 10 이 그 결과다. 완전히 떼려면 copy.deepcopy.

04기출

왜 이걸 하나첨자 줄과 중간 결과를 적으며 푸는 연습이다. 암산이 Python 오답의 첫째 원인이다.

슬라이스가 보이면 문자열 위에 첨자를 적고, 메서드가 이어지면 줄마다 중간 값을 적는다. 집합 · 딕셔너리는 연산마다 현재 내용을 다시 적는다.

2023-1회 · 15번Python

다음 Python 프로그램의 실행 결과에 남는 집합 원소를 쓰시오. 원소의 출력 순서는 고려하지 않는다.

countries = {'한국', '중국', '일본'} countries.add('베트남') countries.add('중국') countries.remove('일본') countries.update({'홍콩', '한국', '태국'}) print(countries)
정답한국, 중국, 베트남, 홍콩, 태국(순서 무관)

— 한국, 중국, 베트남, 홍콩, 태국 (순서 무관). 집합은 같은 것을 두 번 넣어도 하나만 남기고, remove 로 뺀 일본은 돌아오지 않는다.

원리 — 집합(set)은 "무엇이 있나"만 기억하는 자루다. 같은 원소를 다시 넣어도(add('중국')) 자루 안은 그대로이고, 순서도 없어서 출력 순서는 그때그때 다르다 — 그래서 문제가 "순서는 고려하지 않는다"고 한 것이다. add 는 하나 넣기, remove 는 하나 빼기(없으면 오류), update 는 여러 개를 한꺼번에 넣기다. update 안에 이미 있는 것이 섞여 있어도 새 것만 늘어난다.

따라가기

  1. 처음 {한국, 중국, 일본}
  2. add 베트남 → {한국, 중국, 일본, 베트남}
  3. add 중국 → 이미 있어 변화 없음
  4. remove 일본 → {한국, 중국, 베트남}
  5. update {홍콩, 한국, 태국} → 한국은 있으니 홍콩 · 태국만 추가 → {한국, 중국, 베트남, 홍콩, 태국}

헷갈리는 자리 — 중국이나 한국을 두 번 적는 것(집합에 중복은 없다). 일본을 남기는 것(remove 뒤 update 에 일본은 없다). 답안에는 원소 다섯을 쉼표로 적으면 되고 중괄호 · 순서는 채점 대상이 아니다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2023-2회 · 5번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

text = "engineer information processing" first = text[:3] second = text[4:6] third = text[28:] print(first + second + third)
정답engneing

— engneing. 세 조각 eng · ne · ing 을 이어 붙인 것이다.

원리 — 슬라이스 text[a:b] 는 첨자 a 부터 b 까지다. 첨자는 0 부터 세고 b 번째 글자는 안 들어간다. [:3] 은 처음부터 첨자 2 까지 세 글자, [4:6] 은 첨자 4 · 5 두 글자, [28:] 은 첨자 28 부터 끝까지다. 첨자를 세려면 문자열 위에 0 1 2 … 를 적는다. 공백도 한 칸이다.

따라가기 — "engineer information processing" (공백 포함 31글자).

  1. [:3] → 첨자 0 · 1 · 2 → eng
  2. [4:6] → 첨자 4 · 5 → e n g i n ene
  3. [28:] → 첨자 28 부터 끝 → "processing" 의 마지막 세 글자 → ing
  4. 이어서 engneing

헷갈리는 자리 — [4:6] 을 첨자 4 · 5 · 6 세 글자로 세어 "nee" 를 넣는 것. 끝 첨자는 빠진다. [28:] 을 세다 공백을 빼먹으면 자리가 밀린다 — "engineer"(8) + 공백(1) + "information"(11) + 공백(1) = 21 이라 "processing" 은 첨자 21 부터, 28 은 그중 8번째 글자 i 부터다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2023-3회 · 16번Python

공백으로 구분해 입력한 두 값을 각각 저장하려고 한다. Python 코드의 ①에 들어갈 메서드 이름을 쓰시오.

first, second = input().( ① )() first = int(first) second = int(second) print(first, second)
정답split

split. 한 줄로 받은 입력을 공백 기준으로 잘라 리스트로 만드는 메서드다.

원리input() 은 사용자가 친 한 줄을 통째로 문자열 하나로 돌려준다. "3 5" 처럼 두 값을 공백으로 나눠 쳤다면 그것을 잘라야 하는데, split() 은 인자를 안 주면 공백(연속된 공백도 하나로)을 기준으로 잘라 리스트 ['3', '5'] 를 돌려준다. 리스트의 원소 수와 왼쪽 변수 수가 같으면 first, second = … 로 한 번에 나눠 담을 수 있다(언패킹). 잘린 조각은 아직 문자열이라 int() 로 숫자로 바꾼다.

따라가기

  1. input() → "3 5"
  2. .split() → ['3', '5'] → first = '3', second = '5'
  3. int 변환 → 3, 5 → print → 3 5

헷갈리는 자리strip(양끝 공백 제거)이나 join(반대로 붙이기)을 적는 것. "나누다"는 split 이다. 괄호 안에 ' ' 를 넣어도 되지만 답은 메서드 이름만이다. 세 값을 쳤다면 변수 두 개에 못 담아 오류가 난다는 것도 같은 문법의 함정이다.

빈칸에 답을 넣어 Python 3.12 로 실행해 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2024-1회 · 12번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

words = ["Seoul", "Kyeonggi", "Incheon", "Daejun", "Daegu", "Pusan"] result = "S" for word in words: result += word[1] print(result)
정답Seynaau

— Seynaau. 처음 S 에 각 도시 이름의 둘째 글자를 차례로 이어 붙인다.

원리 — 문자열도 첨자로 글자 하나를 꺼낼 수 있고 첨자는 0 부터라 word[1] 은 둘째 글자다. result += word[1] 은 "지금 result 뒤에 그 글자를 붙여라"다. for 문은 리스트의 도시 이름을 순서대로 하나씩 word 에 넣는다. 결국 답은 "S" + (각 이름의 둘째 글자들)이다.

따라가기

  1. Seoul → e, Kyeonggi → y, Incheon → n
  2. Daejun → a, Daegu → a, Pusan → u
  3. "S" + "eynaau" → Seynaau

헷갈리는 자리 — word[1] 을 첫 글자로 읽어 "SSKIDDP" 를 적는 것. 0 이 첫 글자, 1 이 둘째다. 처음의 "S" 를 빼먹고 eynaau 만 적는 것 — result 의 초기값이 답의 첫 글자다. 대소문자도 그대로 적는다. 문자열은 바꿀 수 없는 값이라 += 는 원래 문자열을 고치는 것이 아니라 새 문자열을 만들어 result 에 다시 담는 것인데, 결과만 보면 "뒤에 붙이기"로 읽어도 된다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2024-2회 · 8번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

text = "ab3ca3a3" print(text[:2] + str(text.count("3")) + text[3:5] + str(text.count("a")))
정답ab3ca3

— ab3ca3. 슬라이스 두 조각과 count 로 센 개수 두 개를 문자열로 이어 붙인다.

원리text[:2] 는 첨자 0 · 1(ab), text[3:5] 는 첨자 3 · 4(ca)다. text.count("3") 은 문자열 안에 "3" 이 몇 번 있는지 세어 정수를 돌려주므로, 문자열과 붙이려면 str() 로 글자로 바꿔야 한다 — 그래서 코드에 str 이 붙어 있다. 네 조각을 + 로 이으면 답이다. "ab3ca3a3" 에서 3 은 첨자 2 · 5 · 7 에 세 번, a 는 첨자 0 · 3 · 6 에 세 번 있다.

따라가기

  1. text[:2] → ab
  2. count("3") → 3
  3. text[3:5] → ca
  4. count("a") → 3. 이어서 ab3ca3

헷갈리는 자리 — text[3:5] 를 첨자 3 · 4 · 5 로 세어 "ca3" 을 넣는 것. 끝 첨자는 빠진다. count 를 첨자 위치로 착각하는 것 — count 는 개수다. str() 없이 정수를 문자열에 더하면 TypeError 라, 빈칸 문제로 str 이 나올 수도 있다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2024-3회 · 2번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

def reverse(values): for i in range(len(values) // 2): values[i], values[-i - 1] = values[-i - 1], values[i] values = [1, 2, 3, 4, 5, 6] reverse(values) print(sum(values[::2]) - sum(values[1::2]))
정답3

— 3. 리스트를 뒤집어 6 5 4 3 2 1 로 만든 뒤 짝수 첨자 값의 합에서 홀수 첨자 값의 합을 뺀다.

원리values[i], values[-i - 1] = values[-i - 1], values[i] 는 임시 변수 없이 두 칸을 맞바꾸는 파이썬 관용구다. 음수 첨자 −1 은 마지막, −2 는 그 앞이다. i 를 0 부터 길이의 절반(3)까지 돌리면 앞 3개와 뒤 3개가 짝지어 바뀌어 전체가 뒤집힌다. 리스트는 함수에 참조로 넘어가 원본이 바뀐다. [::2] 는 첨자 0 · 2 · 4, [1::2] 는 1 · 3 · 5 를 고른 것이다.

따라가기

  1. i = 0: 0번 ↔ −1번 → [6, 2, 3, 4, 5, 1]
  2. i = 1: 1번 ↔ −2번 → [6, 5, 3, 4, 2, 1]
  3. i = 2: 2번 ↔ −3번 → [6, 5, 4, 3, 2, 1]
  4. [::2] = 6, 4, 2 → 12. [1::2] = 5, 3, 1 → 9
  5. 12 − 9 = 3

헷갈리는 자리 — reverse 가 원본을 안 바꾼다고 보고 원래 순서로 계산하면 (1 + 3 + 5) − (2 + 4 + 6) = −3 이다. 리스트는 참조라 바뀐다. 슬라이스 스텝 2 를 "두 개씩"이 아니라 "두 번째부터"로 읽는 것도 흔하다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2024-3회 · 14번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

def value_of(value): if type(value) == type(100): return 100 elif type(value) == type(""): return len(value) return 20 a = "100.0" b = 100.0 c = (100, 200) print(value_of(a) + value_of(b) + value_of(c))
정답45

— 45. "100.0" 은 문자열이라 길이 5, 100.0 은 실수라 20, 튜플도 20 이다.

원리type(x) 는 값의 자료형을 돌려준다. type(100) 은 int, type("") 은 str 이다. 따옴표로 감싼 "100.0" 은 숫자처럼 보여도 문자열이고, 100.0 은 소수점이 있어 float, (100, 200) 은 튜플이다. 함수는 int 면 100, str 이면 글자 수, 그 밖은 20 을 돌려준다. 세 값 중 int 는 하나도 없다는 것이 이 문제의 핵심이다.

따라가기

  1. a = "100.0" → str → len = 5 (1, 0, 0, ., 0)
  2. b = 100.0 → float → int 도 str 도 아님 → 20
  3. c = (100, 200) → tuple → 20
  4. 5 + 20 + 20 = 45

헷갈리는 자리 — "100.0" 을 숫자로 보고 100 을 더하는 것. 따옴표가 있으면 문자열이다. 100.0 을 int 로 보는 것 — float 는 int 와 다른 자료형이다. 길이를 셀 때 소수점도 한 글자다(4 가 아니라 5).

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2025-1회 · 17번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

class Node: def __init__(self, value): self.value = value self.children = [] def build(values): nodes = [Node(value) for value in values] for index in range(1, len(nodes)): nodes[(index - 1) // 2].children.append(nodes[index]) return nodes[0] def odd_level_sum(node, level=0): own = node.value if level % 2 == 1 else 0 return own + sum(odd_level_sum(child, level + 1) for child in node.children) root = build([3, 5, 8, 12, 15, 18, 21]) print(odd_level_sum(root))
정답13

— 13. 트리에서 레벨이 홀수(1)인 노드는 5 와 8 뿐이고 그 합이 13 이다.

원리 — build 는 리스트를 이진 트리로 만든다. 첨자 index 의 노드를 첨자 (index − 1) // 2 노드의 자식으로 붙이므로, 0 아래에 1 · 2, 1 아래에 3 · 4, 2 아래에 5 · 6 이 온다(힙과 같은 배열 표현). odd_level_sum 은 루트를 레벨 0 으로 두고 자식으로 내려갈 때마다 레벨을 1 씩 올리며, 레벨이 홀수인 노드의 값만 더한다. level=0 은 기본값 인자라 루트 호출 때 안 적어도 0 이다.

따라가기 — 값 [3, 5, 8, 12, 15, 18, 21].

  1. 레벨 0: 3(첨자 0)
  2. 레벨 1: 5(첨자 1), 8(첨자 2) → 홀수 레벨
  3. 레벨 2: 12, 15(5 의 자식), 18, 21(8 의 자식) → 짝수 레벨
  4. 홀수 레벨 합 5 + 8 = 13

헷갈리는 자리 — 루트를 레벨 1 로 세어 3 + 12 + 15 + 18 + 21 = 69 를 적는 것. 코드가 level=0 에서 시작한다. 자식을 붙이는 규칙을 몰라 트리를 한 줄 사슬로 보는 것 — (index − 1) // 2 가 부모라는 것을 표로 확인한다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2025-2회 · 17번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

numbers = [1, 2, 3] doubled = {number: number * 2 for number in numbers} saved = set(doubled.values()) numbers[0] = 99 doubled[2] = 7 saved.add(99) print(len(saved & set(doubled.values())))
정답2

— 2. saved 는 만들 때의 값 {2, 4, 6} 을 복사한 것이라 나중의 변경이 안 따라오고, 마지막 값들 {2, 7, 6} 과의 교집합은 {2, 6} 이다.

원리 — 딕셔너리 컴프리헨션 {number: number * 2 for …} 는 {1: 2, 2: 4, 3: 6} 을 만든다. set(doubled.values()) 는 그 순간의 값들을 새 집합에 복사해 담는다 — 나중에 doubled 를 고쳐도 saved 는 모른다. numbers[0] = 99 는 이미 만들어진 딕셔너리와 무관하다. doubled[2] = 7 은 키 2 의 값을 4 에서 7 로 바꾼다. & 는 두 집합에 모두 있는 원소만 남기는 교집합이다.

따라가기

  1. doubled = {1: 2, 2: 4, 3: 6}, saved = {2, 4, 6}
  2. numbers[0] = 99 → 영향 없음
  3. doubled[2] = 7 → 값들 {2, 7, 6}
  4. saved.add(99) → {2, 4, 6, 99}
  5. {2, 4, 6, 99} & {2, 7, 6} = {2, 6} → len 2

헷갈리는 자리 — saved 가 doubled 를 계속 바라본다고 보고 {2, 7, 6, 99} 로 계산해 3 을 적는 것. set(…) 은 그 순간 복사다. numbers 를 바꾸면 doubled 도 바뀐다고 보는 것 — 컴프리헨션은 이미 끝났다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2025-3회 · 20번Python

다음 Python 프로그램의 출력에서 빈칸 ①부터 ⑧에 들어갈 값을 순서대로 쓰시오.

data = [ [3, 5, 2, 4, 1], [4, 5, 1], [4, 4, 1, 5, 4], [4, 5] ] result = {} for index, values in enumerate(data): result[index] = (sum(values), len(values)) print(result)

[출력] {0: (①, ②), 1: (③, ④), 2: (⑤, ⑥), 3: (⑦, ⑧)}

정답① 15 ② 5 ③ 10 ④ 3 ⑤ 18 ⑥ 5 ⑦ 9 ⑧ 2

— ① 15 ② 5 ③ 10 ④ 3 ⑤ 18 ⑥ 5 ⑦ 9 ⑧ 2. 안쪽 리스트마다 (합, 길이) 튜플을 만들어 첨자를 키로 넣은 딕셔너리다.

원리enumerate(data) 는 리스트를 돌면서 (첨자, 원소) 짝을 준다. 그래서 index 는 0 · 1 · 2 · 3, values 는 안쪽 리스트다. (sum(values), len(values)) 는 합과 개수를 튜플로 묶은 것이고, 튜플은 소괄호로 찍힌다. 딕셔너리는 넣은 순서대로 찍히므로 출력은 키 0 부터다. 빈칸은 각 튜플의 합이 먼저, 길이가 나중이다.

따라가기

  1. 0: [3, 5, 2, 4, 1] → 합 15, 길이 5
  2. 1: [4, 5, 1] → 10, 3
  3. 2: [4, 4, 1, 5, 4] → 18, 5
  4. 3: [4, 5] → 9, 2

헷갈리는 자리 — 튜플 안의 순서를 (길이, 합)으로 바꿔 적는 것. 코드에서 sum 이 앞이다. 덧셈 실수(셋째 줄 4 + 4 + 1 + 5 + 4 = 18)를 한 칸이라도 하면 그 칸만 틀리므로 여덟 칸을 하나씩 검산한다. 답은 여덟 값을 순서대로 적는다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2026-1회 · 10번Python

다음 Python 프로그램을 실행했을 때 출력되는 값을 쓰시오.

lst = list(range(10)) for c in lst[::-2]: print(c, end='A') print()
정답9A7A5A3A1A

— 9A7A5A3A1A. 0~9 리스트를 뒤에서부터 두 칸씩 건너뛰며 꺼내고, 각 값 뒤에 A 를 붙여 한 줄로 찍는다.

원리list(range(10)) 은 [0, 1, …, 9] 다. 슬라이스 [::-2] 는 시작 · 끝을 비우고 스텝을 −2 로 준 것이라 "끝에서 시작해 두 칸씩 거꾸로"다 — 9, 7, 5, 3, 1. print(c, end='A') 는 줄바꿈 대신 A 를 붙이라는 뜻이라 값들이 한 줄에 A 로 이어진다. 마지막 print() 는 빈 줄바꿈 하나를 찍어 줄을 끝낸다.

따라가기

  1. lst[::-2] → [9, 7, 5, 3, 1]
  2. 9 → "9A", 7 → "7A", 5 → "5A", 3 → "3A", 1 → "1A"
  3. 한 줄에 이어 9A7A5A3A1A, 그 뒤 줄바꿈

헷갈리는 자리 — 스텝이 음수인데 앞에서부터 0, 2, 4, … 로 적는 것. 음수 스텝은 시작이 끝이다. 마지막 1 뒤의 A 를 빼먹는 것 — end='A' 는 매 print 마다 붙는다. 마지막 print() 는 글자를 안 찍으므로 답에 들어가지 않는다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2026-1회 · 12번Python

다음 Python 프로그램을 실행했을 때 출력되는 값을 쓰시오.

def f(a): m = [[x] for x in a] b = m[:] for i in range(len(b) - 1): b[i + 1] += b[i] return sum(len(x) for x in m) print(f([1, 2, 3, 4]))
정답10

— 10. b = m[:] 는 겉 리스트만 새로 만들고 안쪽 리스트는 공유하므로, b 를 통해 늘린 안쪽 리스트가 m 에서도 보여 길이 합이 1 + 2 + 3 + 4 다.

원리m = [[x] for x in a] 는 [[1], [2], [3], [4]] 다. m[:] 는 얕은 복사 — 바깥 상자는 새로 만들지만 그 안의 네 리스트는 원래 것을 그대로 가리킨다. 그래서 b[i + 1] += b[i] 는 안쪽 리스트를 제자리에서 늘리는 것이고(리스트의 += 는 새 리스트를 만들지 않고 이어 붙인다), 그 안쪽 리스트는 m 도 같이 들고 있다. 결국 m 의 안쪽 리스트들이 [1], [2, 1], [3, 2, 1], [4, 3, 2, 1] 로 커진다. 완전히 떼어 놓으려면 copy.deepcopy 가 필요하다.

따라가기

  1. i = 0: b[1] += b[0] → [2] + [1] → [2, 1]
  2. i = 1: b[2] += b[1] → [3, 2, 1]
  3. i = 2: b[3] += b[2] → [4, 3, 2, 1]
  4. m 의 길이들 1 + 2 + 3 + 4 = 10

헷갈리는 자리 — [:] 를 완전 복사로 보고 m 이 안 바뀌었다며 4 를 적는 것. 얕은 복사는 안쪽을 공유한다. 반대로 b[i + 1] = b[i + 1] + b[i] 였다면 새 리스트가 만들어져 b 만 바뀌고 답은 4 였다 — += 와 = 의 차이까지가 이 문제다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2026-1회 · 14번Python

다음 Python 프로그램에 HumanDev를 입력했을 때 출력되는 값을 쓰시오.

i = input() x = [] for word in i.split(): x.append(word) y = ''.join(x) z = ''.join(c for c in y[::-1] if c not in 'ong') print(z)
정답veDamuH

— veDamuH. 입력 HumanDev 를 뒤집어 veDnamuH 로 만든 뒤 o · n · g 를 빼면 n 하나가 빠진다.

원리i.split() 은 공백으로 나누는데 "HumanDev" 엔 공백이 없어 조각이 하나뿐이고, ''.join(x) 로 다시 붙여도 그대로다. y[::-1] 은 문자열 뒤집기. ''.join(c for c in … if c not in 'ong') 은 글자를 하나씩 보며 o · n · g 중 하나가 아닌 것만 모아 붙인다. 대소문자를 구분하므로 대문자 D · H 는 그대로 남는다.

따라가기

  1. x = ["HumanDev"], y = "HumanDev"
  2. y[::-1] → "veDnamuH"
  3. o · n · g 제거 → n 하나 빠짐 → veDamuH

헷갈리는 자리 — 뒤집기 전에 글자를 빼거나, 'ong' 을 한 덩어리 문자열로 보고 "ong" 이 없으니 아무것도 안 뺀다고 보는 것 — c not in 'ong' 은 글자 하나가 그 세 글자 중 하나인지다. 대문자 H 는 안 빠진다는 것도 확인한다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2026-2회 · 5번Python

다음 Python 프로그램의 실행 결과를 쓰시오.

str = "_THIS_IS_KIM_SPEAKING" a = str[:4] b = str[6:8] c = str[18:] ans = a + b + c print(ans)
정답_THIISING

— _THIISING. 세 조각 _THI · IS · ING 을 이은 것이다.

원리 — 슬라이스 [a:b] 는 a 부터 b 앞까지, 첨자는 0 부터다. 밑줄 _ 도 글자 하나로 센다. "_THIS_IS_KIM_SPEAKING" 위에 첨자를 적으면 0 은 _, 1 T, 2 H, 3 I, 4 S, 5 _, 6 I, 7 S, 8 _, 9 K, 10 I, 11 M, 12 _, 13 S, 14 P, 15 E, 16 A, 17 K, 18 I, 19 N, 20 G 다. str 이라는 이름은 내장 타입 이름을 덮어쓴 것이라 좋은 코드는 아니지만 동작에는 문제가 없다.

따라가기

  1. [:4] → 첨자 0~3 → _THI
  2. [6:8] → 첨자 6 · 7 → IS
  3. [18:] → 첨자 18 부터 끝 → ING
  4. 이어서 _THIISING

헷갈리는 자리 — 밑줄을 안 세어 첨자가 하나씩 밀리는 것과, [6:8] 을 세 글자로 세는 것. 답의 맨 앞 밑줄을 빼먹고 THIISING 으로 적으면 틀린다 — [:4] 의 첫 글자가 밑줄이다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

2026-2회 · 13번Python

다음 Python 프로그램의 실행 결과를 쓰시오. 딕셔너리는 작성된 순서대로 순회한다고 가정한다.

class LocationDict: locations = { "NYC": "New York", "LON": "London", "PAR": "Paris", "TKY": "Tokyo" } obj = LocationDict() result = "" for key, location in obj.locations.items(): result += key[-1] + location[0] print(result, end="")
정답CNNLRPYT

— CNNLRPYT. 딕셔너리를 넣은 순서로 돌며 키의 마지막 글자와 값의 첫 글자를 번갈아 붙인다.

원리 — 클래스 안에 바로 적은 locations 는 클래스 변수라 객체 obj 로도 그대로 읽힌다. items() 는 (키, 값) 짝을 순서대로 준다 — 파이썬 3.7 부터 딕셔너리는 넣은 순서를 지킨다(문제도 그렇게 가정한다고 적었다). key[-1] 은 음수 첨자라 키의 마지막 글자, location[0] 은 값의 첫 글자다. end="" 는 마지막 줄바꿈을 없앤 것이라 출력 내용은 같다.

따라가기

  1. NYC → New York: C + N
  2. LON → London: N + L
  3. PAR → Paris: R + P
  4. TKY → Tokyo: Y + T → 이어서 CNNLRPYT

헷갈리는 자리 — key[-1] 을 첫 글자로 읽어 NNLLPPTT 를 적는 것. −1 은 마지막이다. 값의 첫 글자를 소문자로 적거나 "New York" 의 공백 뒤 Y 를 고르는 것 — [0] 은 맨 앞 N 이다. 순서를 알파벳순으로 다시 세우지 않는다.

복원 코드를 Python 3.12 로 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.

예상 1회 · 4번Python빈출 원리: 딕셔너리 + 정렬

다음 코드의 출력을 쓰시오.

d = {'a': 3, 'b': 1, 'c': 2} s = sorted(d, key=d.get) print(s[0], d[s[-1]])
정답b 3

— b 3. 값 순으로 키를 정렬하면 b · c · a 이고, 첫 키는 b, 마지막 키 a 의 값은 3 이다.

원리 — sorted(d) 는 딕셔너리의 "키"를 정렬한 리스트를 돌려준다. key=d.get 을 주면 각 키를 d.get(키) 곧 그 키의 값으로 바꿔 비교한다 — 키가 아니라 값 순으로 키가 줄 서는 것이다. 값이 1 · 2 · 3 인 b · c · a 순서다. s[0] 은 'b', s[-1] 은 마지막 'a' 이고 d['a'] = 3 이다. "값으로 정렬한 키 목록"은 최댓값 키 찾기(max(d, key=d.get))와 같은 발상이라 함께 나온다.

따라가기

  1. d.get 기준: b(1) → c(2) → a(3) → s = ['b', 'c', 'a']
  2. s[0] → b
  3. s[-1] = 'a' → d['a'] → 3

헷갈리는 자리 — sorted(d) 가 값을 정렬한다고 보는 것. 딕셔너리를 순회하면 키가 나오므로 정렬 대상도 키다. key=d.get 이 없으면 알파벳 순 a · b · c 라 답이 "a 2" 로 바뀐다. sorted(d.values()) 였다면 [1, 2, 3] 이다 — 무엇을 정렬하는지 먼저 본다.

이전 자료의 예상 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

예상 1회 · 7번Python빈출 원리: 컴프리헨션 + 세트

다음 코드의 출력을 쓰시오.

a = [x for x in range(1, 10) if x % 3 == 0] b = set(a + [3, 9, 12]) print(len(a), len(b), sum(a))
정답3 4 18

— 3 4 18. a 는 [3, 6, 9] 로 3개, 집합 b 는 중복이 사라져 {3, 6, 9, 12} 4개, a 의 합은 18 이다.

원리 — 리스트 컴프리헨션 [x for x in range(1, 10) if x % 3 == 0] 은 1~9 중 3 의 배수만 모은다 → [3, 6, 9]. a + [3, 9, 12] 는 리스트 이어 붙이기라 [3, 6, 9, 3, 9, 12] 인데, set() 에 넣으면 중복이 제거되어 {3, 6, 9, 12} 가 된다. 집합은 "중복 없음 · 순서 없음"이 성질이다. len(a) = 3, len(b) = 4, sum(a) = 3 + 6 + 9 = 18. range 의 끝 10 은 포함되지 않는다는 것도 확인 지점이다.

따라가기

  1. a = [3, 6, 9] → len 3
  2. a + [3, 9, 12] = [3, 6, 9, 3, 9, 12] → set → {3, 6, 9, 12} → len 4
  3. sum(a) = 18

헷갈리는 자리 — set 이 중복을 지운다는 것을 놓쳐 len(b) 를 6 으로 적는 것. 그리고 sum 의 대상이 a 인지 b 인지 — b 였다면 30 이다. 집합 문제는 "중복 제거 후 몇 개"가 늘 핵심이다.

이전 자료의 예상 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

예상 2회 · 4번Python빈출 원리: 문자열 메서드 체인

다음 코드의 출력을 쓰시오.

s = "Hello World" print(s.upper().count('L'), s.split()[1][::-1].lower())
정답3 dlrow

— 3 dlrow. 대문자로 바꾼 "HELLO WORLD" 에 L 이 3개, 둘째 단어 "World" 를 뒤집어 소문자로 만들면 dlrow 다.

원리 — 문자열 메서드는 원본을 바꾸지 않고 새 문자열을 돌려주므로 점으로 이어 부른다. s.upper() 는 "HELLO WORLD", .count('L') 은 L 의 개수 — HELLO 에 2개, WORLD 에 1개로 3. s.split() 은 공백 기준으로 ["Hello", "World"], [1] 은 "World", [::-1] 은 뒤집기 "dlroW", .lower() 는 "dlrow". 각 단계의 결과를 다음 단계가 받는 파이프라인이다.

따라가기

  1. "HELLO WORLD".count('L') → 3
  2. split()[1] → "World" → [::-1] → "dlroW" → lower → dlrow

헷갈리는 자리 — count 를 대문자로 바꾸기 전 원본 "Hello World" 에서 세어 'L' 이 0 개(소문자 l 만 있음)라고 하는 것. upper() 가 먼저다. 그리고 lower() 를 뒤집기 전에 적용해도 결과는 같지만, [::-1] 이 문자열 전체가 아니라 split 한 한 단어에 걸린다는 것을 본다.

이전 자료의 예상 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

예상 2회 · 7번Python빈출 원리: lambda + map

다음 코드의 출력을 쓰시오.

a = [1, 2, 3, 4, 5] b = list(map(lambda x: x * x, a)) print(b[2] + b[-1])
정답34

— 34. 제곱 리스트 [1, 4, 9, 16, 25] 에서 b[2] = 9 와 b[-1] = 25 를 더했다.

원리 — map(함수, 리스트) 는 리스트의 각 원소에 함수를 적용한 결과를 만든다. lambda x: x * x 는 이름 없는 제곱 함수다. map 이 돌려주는 것은 게으른 반복자라 list() 로 감싸야 리스트가 된다 → [1, 4, 9, 16, 25]. b[2] 는 세 번째 원소 9, b[-1] 은 마지막 25. 합 34. 음수 첨자는 뒤에서부터 센다 — −1 이 마지막, −2 가 그 앞이다.

따라가기

  1. b = [1, 4, 9, 16, 25]
  2. b[2] = 9, b[-1] = 25
  3. 9 + 25 = 34

헷갈리는 자리 — b[2] 를 "2번째"로 읽어 4 로 잡는 것. 첨자는 0 부터라 b[2] 는 세 번째다. lambda x: x * 2 로 잘못 읽으면 [2, 4, 6, 8, 10] 이라 6 + 10 = 16 이 된다 — x * x 와 x * 2 를 구분한다.

이전 자료의 예상 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

05틀리는 자리

왜 이걸 하나 — Python 은 첨자 하나 · 괄호 하나에서 깎인다.

자리새는 꼴적는 법기출
끝 첨자s[6:8] 을 세 글자로b 앞까지. 글자 수는 b − a2026-2회 5번 · 2023-2회 5번
음수 스텝[::-2] 를 앞에서부터 센다뒤에서 시작. 마지막 · 그 두 칸 앞 …2026-1회 10번
집합 출력중복을 남기거나 순서를 답으로중복은 지우고, 순서는 「무관」이면 아무 순서2023-1회 15번
저장 시점set(doubled.values()) 가 나중 변경을 따라간다고만들 때 값을 복사한 것. 뒤의 변경은 무관2025-2회 17번
얕은 복사[:] 를 완전 복사로안쪽 리스트는 공유2026-1회 12번
출력 꼴튜플 · 리스트의 괄호 · 쉼표 · 공백을 빼먹는다print 는 (15, 5) · [1, 2] 꼴 그대로. end='' 면 줄바꿈 없음2025-3회 20번 · 2026-2회 13번
글자 고르기word[1] 을 첫 글자로0 이 첫 글자, 1 이 둘째, −1 이 마지막2024-1회 12번 · 2026-2회 13번
입력input 값의 공백 · 대소문자를 바꾼다문제가 준 입력 그대로 넣고 따라간다2026-1회 14번

Python 답은 print 가 찍는 꼴 그대로다. 리스트면 대괄호와 쉼표 뒤 공백까지, 튜플이면 소괄호까지 답이다.

06훈련

왜 이걸 하나 — 규칙 9개를 짧은 코드 30개로 돌린다. 하루 5문항이면 6일치다.

훈련 30문항은 슬라이싱 3종부터 sort · append, 딕셔너리 · 집합 연산, strip · count · replace, range 스텝, 컴프리헨션, split · join, 기본값 매개변수, *args, 클래스와 상속, 지역 변수, 불변성, 참조 vs 복사, sorted key, enumerate · zip, 예외, bool 산술, max key, 중첩 리스트, pop · insert, get 기본값, lambda 정렬까지다. 정답은 전부 Python 3.12 로 실행해 확인했다.

훈련 Python · 1번Python슬라이싱 3종
s = "PROGRAMMING" print(s[3:7], s[-4:], s[::3])
정답GRAM MING PGMN

— GRAM MING PGMN. 슬라이스 세 가지 — 범위, 뒤에서부터, 건너뛰기다.

원리 — s[a:b] 는 a 부터 b 앞까지, s[-n:] 은 뒤에서 n 글자, s[::k] 는 처음부터 k 칸씩 건너뛰며 고른다. "PROGRAMMING" 에 번호를 붙이면 P0 R1 O2 G3 R4 A5 M6 M7 I8 N9 G10 이다. [3:7] 은 3 · 4 · 5 · 6 = GRAM. [-4:] 는 뒤 네 글자 MING. [::3] 은 0 · 3 · 6 · 9 = P G M N. 슬라이스는 끝 미포함, 음수는 뒤에서부터, 세 번째 값은 보폭이다.

따라가기

  1. s[3:7] → 3~6 → GRAM
  2. s[-4:] → 뒤 4개 → MING
  3. s[::3] → 0 · 3 · 6 · 9 → PGMN

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 2번Pythonsort + append
a = [5, 3, 8, 1] a.sort() a.append(4) print(a[2], a[-1], len(a))
정답5 4 5

— 5 4 5. 정렬한 뒤 4 를 뒤에 붙여 [1, 3, 5, 8, 4] 가 되고, 세 번째 5, 마지막 4, 길이 5 다.

원리 — a.sort() 는 리스트 자체를 오름차순으로 바꾼다(제자리, 반환값 None). 그 뒤 append(4) 는 정렬 여부와 상관없이 맨 뒤에 붙인다 — 그래서 4 가 끝에 가 정렬이 깨진다. a[2] 는 세 번째 원소 5, a[-1] 은 마지막 4, len 은 5 다. "sort 뒤 append 는 정렬을 유지하지 않는다"는 것이 이 문제의 함정이다.

따라가기

  1. sort → [1, 3, 5, 8]
  2. append(4) → [1, 3, 5, 8, 4]
  3. a[2] 5 · a[-1] 4 · len 5 → 5 4 5

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 3번Python딕셔너리 추가 + in
d = {'x': 1, 'y': 2} d['z'] = d['x'] + d['y'] print(d['z'], len(d), 'y' in d)
정답3 3 True

— 3 3 True. 새 키 z 에 1 + 2 = 3 을 넣어 항목이 3개가 되고, 'y' 는 키에 있다.

원리 — d[키] = 값 은 키가 없으면 새 항목을 만들고 있으면 덮어쓴다. d['z'] = d['x'] + d['y'] 는 1 + 2 = 3 을 z 에 넣는다. len(d) 는 키 개수라 x · y · z 로 3. 'y' in d 는 "키 중에 y 가 있나"라 True — 값이 아니라 키를 본다(값을 보려면 in d.values()). 딕셔너리는 키로 찾는 자료형이라는 것이 세 답 전부에 깔려 있다.

따라가기

  1. d['z'] = 3 → d = {x:1, y:2, z:3}
  2. len → 3, 'y' in d → True
  3. 출력 3 3 True

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 4번Python집합 연산 3종
a = {1, 2, 3, 4} b = {3, 4, 5} print(sorted(a & b), sorted(a | b), sorted(a - b))
정답[3, 4] [1, 2, 3, 4, 5] [1, 2]

— [3, 4] [1, 2, 3, 4, 5] [1, 2]. 교집합 · 합집합 · 차집합이다.

원리 — 집합 연산자는 수학 기호 그대로다. & 는 교집합(둘 다 있는 것) {3, 4}, | 는 합집합(하나라도 있는 것) {1, 2, 3, 4, 5}, − 는 차집합(a 에만 있는 것) {1, 2}. 집합은 순서가 없어 출력 순서가 보장되지 않으므로 sorted() 로 리스트로 바꿔 정렬해 찍는다 — 그래서 답이 대괄호다. b − a 였다면 {5} 라는 것도 방향을 확인하는 포인트다.

따라가기

  1. a & b → [3, 4]
  2. a | b → [1, 2, 3, 4, 5]
  3. a − b → [1, 2]

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 5번Pythonstrip / count / replace
s = " Hello Python " t = s.strip() print(t.count('o'), t.replace('o', '0'))
정답2 Hell0 Pyth0n

— 2 Hell0 Pyth0n. 양끝 공백을 떼면 "Hello Python" 이고 o 가 2개, o 를 0 으로 바꾼 것이다.

원리 — strip() 은 문자열 양쪽 끝의 공백을 지운 새 문자열을 준다(가운데 공백은 그대로). t.count('o') 는 소문자 o 의 개수 — Hello 에 1, Python 에 1 로 2. t.replace('o', '0') 은 모든 o 를 숫자 0 으로 바꾼다 → "Hell0 Pyth0n". 문자열은 불변이라 t 자체는 안 바뀌고 새 문자열이 나온다. 대문자 P 의 'o' 는 없으니 대소문자 구분을 기억한다.

따라가기

  1. strip → "Hello Python"
  2. count('o') → 2
  3. replace → Hell0 Pyth0n

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 6번Pythonrange 스텝 + 루프 후 변수값
total = 0 for i in range(1, 10, 2): total += i print(total, i)
정답25 9

— 25 9. 1 · 3 · 5 · 7 · 9 의 합 25 이고, 반복이 끝난 뒤 i 는 마지막 값 9 로 남는다.

원리 — range(1, 10, 2) 는 1 부터 10 앞까지 2 씩 → 1 3 5 7 9. 합은 25. 파이썬의 for 변수는 반복이 끝나도 사라지지 않고 마지막에 받은 값을 유지한다 — C 의 for (int i…) 와 달리 블록 밖에서도 보인다. 그래서 print 의 i 는 9 다. 홀수 n 개의 합은 n² 이라 5² = 25 로 검산된다.

따라가기

  1. 1 + 3 + 5 + 7 + 9 = 25
  2. 마지막 i = 9
  3. 출력 25 9

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 7번Pythonwhile + //= 누적 나눗셈
n = 27 c = 0 while n > 1: n //= 3 c += 1 print(c, n)
정답3 1

— 3 1. 27 을 3 으로 세 번 나누면 1 이 되어 멈춘다.

원리 — //= 는 정수 나눗셈 몫을 다시 대입한다. 27 → 9 → 3 → 1 로 세 번 나누는 동안 c 가 3 이 되고, n 이 1 이 되면 n > 1 이 거짓이라 반복이 끝난다. 이 코드는 "3 으로 몇 번 나눌 수 있나" 곧 log₃ 27 = 3 을 세는 것이다. 마지막 n 이 0 이 아니라 1 인 것은 조건이 n > 1 이기 때문이다 — n > 0 이었다면 한 번 더 돌아 c = 4, n = 0 이 된다.

따라가기

  1. 27 → 9 (c 1) → 3 (c 2) → 1 (c 3)
  2. 1 > 1 거짓 → 종료
  3. 출력 3 1

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 8번Python조건부 컴프리헨션
a = [x * 2 if x % 2 else x for x in range(5)] print(a)
정답[0, 2, 2, 6, 4]

— [0, 2, 2, 6, 4]. 홀수는 2배, 짝수는 그대로다.

원리 — 컴프리헨션 안의 A if 조건 else B 는 원소마다 값을 고르는 조건식이다(필터가 아니라 변환). x % 2 는 홀수면 1(참), 짝수면 0(거짓)이므로 홀수는 x * 2, 짝수는 x 그대로다. range(5) 는 0 1 2 3 4. 0 → 0, 1 → 2, 2 → 2, 3 → 6, 4 → 4. 뒤에 붙는 if(필터)와 앞에 오는 if-else(선택)를 구분하는 것이 이 문제의 요점이다.

따라가기

  1. 0 짝 → 0 · 1 홀 → 2 · 2 짝 → 2
  2. 3 홀 → 6 · 4 짝 → 4
  3. 출력 [0, 2, 2, 6, 4]

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 9번Pythonsplit + 역순 + join
s = "a:b:c" parts = s.split(':') print('-'.join(parts[::-1]))
정답c-b-a

— c-b-a. 콜론으로 쪼갠 뒤 뒤집어 하이픈으로 다시 이었다.

원리 — split(':') 은 구분자를 기준으로 잘라 리스트 ['a', 'b', 'c'] 를 준다. [::-1] 은 보폭 −1 슬라이스로 리스트를 뒤집는다 → ['c', 'b', 'a']. '-'.join(리스트) 는 리스트의 문자열들을 하이픈으로 이어 하나의 문자열로 만든다 → "c-b-a". split 과 join 은 서로 반대 방향의 짝이고, 구분자가 각각 다를 수 있다.

따라가기

  1. split(':') → ['a', 'b', 'c']
  2. [::-1] → ['c', 'b', 'a']
  3. '-'.join → c-b-a

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 10번Python기본값 매개변수
def f(a, b=2): return a * b print(f(3), f(3, 4))
정답6 12

— 6 12. b 를 안 주면 기본값 2 가 쓰여 3 × 2, 주면 3 × 4 다.

원리 — 매개변수에 b=2 처럼 기본값을 두면 호출 때 그 인자를 생략할 수 있고, 생략하면 기본값이 쓰인다. f(3) 은 a = 3, b = 2 로 6. f(3, 4) 는 b = 4 로 12. 기본값이 있는 매개변수는 없는 매개변수 뒤에 와야 한다(def f(b=2, a) 는 오류). 키워드로 f(b=4, a=3) 처럼 순서를 바꿔 넘길 수도 있다. 기본값은 함수를 정의하는 순간 한 번만 계산된다.

따라가기

  1. f(3) → 3 × 2 = 6
  2. f(3, 4) → 3 × 4 = 12
  3. 출력 6 12

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 11번Python*args와 튜플 반환
def f(*a): return len(a), sum(a) print(f(1, 2, 3))
정답(3, 6)

— (3, 6). *a 가 인자 셋을 튜플로 받아 개수 3, 합 6 을 돌려준다.

원리 — 매개변수 앞의 * 는 "넘어온 위치 인자를 전부 튜플 하나로 묶어 받아라"다. f(1, 2, 3) 이면 a = (1, 2, 3). len(a) 는 3, sum(a) 는 6. return 에 값을 쉼표로 둘 적으면 튜플 하나로 묶여 반환되므로 print 는 (3, 6) 이라고 괄호째 찍는다. 인자 개수가 정해지지 않은 함수(print 처럼)를 만들 때 쓰는 문법이다.

따라가기

  1. a = (1, 2, 3)
  2. len 3, sum 6 → 튜플 (3, 6)
  3. 출력 (3, 6)

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 12번Python딕셔너리 순회 + 조건
d = {'a': 2, 'b': 3, 'c': 5} s = 0 k2 = '' for k in d: if d[k] % 2 == 1: s += d[k] k2 += k print(s, k2)
정답8 bc

— 8 bc. 값이 홀수인 b(3) · c(5) 만 골라 합 8, 키를 이어 bc 다.

원리 — for k in d 는 딕셔너리의 키를 차례로 준다(3.7 이후 삽입 순서 보장). d[k] % 2 == 1 은 값이 홀수인지 본다 — a 의 2 는 짝수라 제외, b 의 3 과 c 의 5 는 홀수라 포함. s 에는 값을 더하고 k2 에는 키 문자열을 이어 붙인다. 순서가 a → b → c 이므로 k2 는 "bc" 다. 키를 돌며 값을 d[k] 로 꺼내는 것이 딕셔너리 순회의 기본형이다.

따라가기

  1. a: 2 짝수 → 제외
  2. b: 3 → s 3, k2 'b' · c: 5 → s 8, k2 'bc'
  3. 출력 8 bc

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 13번Python클래스 기본 (self)
class P: def __init__(self, n): self.n = n def add(self, x): self.n += x return self.n p = P(5) print(p.add(3), p.n)
정답8 8

— 8 8. add(3) 이 n 을 8 로 바꾸고 8 을 돌려주며, 이어 읽은 p.n 도 8 이다.

원리 — __init__ 은 생성자로 self.n = 5 를 만든다. add 는 self.n 에 x 를 더해 저장하고 그 값을 돌려준다 — 객체 상태를 바꾸는 메서드다. print 의 인자는 왼쪽부터 계산되므로 p.add(3) 이 먼저 실행되어 n 이 8 이 되고 8 을 내며, 그다음 p.n 을 읽으니 역시 8 이다. 만약 순서가 print(p.n, p.add(3)) 이었다면 5 8 이 된다 — 인자 평가 순서가 답을 정한다.

따라가기

  1. p = P(5) → n 5
  2. p.add(3) → n 8, 반환 8
  3. p.n → 8. 출력 8 8

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 14번Python상속 + super()
class A: def f(self): return 1 class B(A): def f(self): return super().f() + 1 print(B().f())
정답2

— 2. B 의 f 가 부모 A 의 f() = 1 에 1 을 더한다.

원리 — class B(A) 는 B 가 A 를 상속한다는 뜻이다. B 가 f 를 다시 정의(오버라이딩)했고, 그 안에서 super().f() 로 부모 A 의 f 를 불러 1 을 받은 뒤 1 을 더한다. 자바의 super.f() 와 같은 역할이다. B().f() 는 B 객체의 f 이므로 B 의 것이 실행되어 2 다. 오버라이딩하면서 부모 동작을 "확장"하는 전형이다.

따라가기

  1. B().f() → B 의 f
  2. super().f() → A 의 f → 1 → 1 + 1
  3. 출력 2

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 15번Python지역 변수 가리기
x = 10 def f(): x = 20 return x print(f(), x)
정답20 10

— 20 10. 함수 안의 x = 20 은 지역 변수라 바깥 x = 10 은 그대로다.

원리 — 함수 안에서 변수에 값을 대입하면 그 변수는 그 함수만의 지역 변수가 된다. 바깥에 같은 이름 x 가 있어도 다른 변수다. f() 는 지역 x 20 을 돌려주고, 밖의 x 는 여전히 10 이다. 바깥 변수를 고치고 싶다면 함수 안에 global x 를 선언해야 한다 — 그러면 x = 20 이 전역 x 를 바꿔 20 20 이 된다. 파이썬 스코프 문제의 기본형이다.

따라가기

  1. f() → 지역 x 20 반환
  2. 전역 x → 10
  3. 출력 20 10

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 16번Python문자열 불변성
s = "abc" t = s s += "d" print(s, t)
정답abcd abc

— abcd abc. 문자열은 불변이라 s += "d" 는 새 문자열을 만들어 s 에만 붙이고, t 는 옛 "abc" 를 계속 가리킨다.

원리 — t = s 로 두 이름이 같은 문자열 객체 "abc" 를 가리킨다. 그런데 문자열은 바꿀 수 없는(불변) 객체라 s += "d" 는 "abc" 를 고치는 것이 아니라 새 객체 "abcd" 를 만들어 s 라는 이름을 거기로 옮기는 것이다. t 는 아무 일도 안 겪어 "abc" 그대로다. 리스트였다면(T-P-17) 같은 코드 모양에서 둘 다 바뀐다 — 가변 · 불변이 답을 가른다.

따라가기

  1. s · t → "abc"
  2. s += "d" → s 만 새 "abcd"
  3. 출력 abcd abc

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 17번Python리스트 참조 vs 복사
a = [1, 2] b = a b.append(3) c = a[:] c.append(4) print(a, c)
정답[1, 2, 3] [1, 2, 3, 4]

— [1, 2, 3] [1, 2, 3, 4]. b 는 a 와 같은 리스트라 append 가 a 에 반영되고, c 는 복사본이라 따로 논다.

원리 — 리스트는 가변 객체다. b = a 는 같은 리스트에 이름을 하나 더 붙인 것이라 b.append(3) 은 a 에도 보인다 → [1, 2, 3]. a[:] 는 전체 슬라이스로 "새 리스트"를 만들어 c 에 담으므로 c.append(4) 는 c 만 바꾼다 → c = [1, 2, 3, 4], a 는 [1, 2, 3]. 자바 배열의 = 와 clone() 관계와 같다. 복사는 a[:] · list(a) · a.copy() 세 가지 모두 된다.

따라가기

  1. b = a → 같은 리스트. append(3) → a = [1, 2, 3]
  2. c = a[:] → 새 리스트. append(4) → c = [1, 2, 3, 4]
  3. 출력 [1, 2, 3] [1, 2, 3, 4]

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 18번Pythonsorted의 key
words = ["banana", "kiwi", "apple"] print(sorted(words, key=len)[0], sorted(words)[0])
정답kiwi apple

— kiwi apple. 길이 순으로는 kiwi(4) 가 가장 짧고, 사전 순으로는 apple 이 먼저다.

원리 — sorted 에 key=len 을 주면 각 원소를 그 길이로 바꿔 비교한다 — banana 6, kiwi 4, apple 5 이므로 kiwi · apple · banana 순이고 [0] 은 kiwi. key 가 없으면 문자열 자체를 사전 순으로 비교해 apple · banana · kiwi 이고 [0] 은 apple. 같은 리스트가 기준 하나로 다른 순서가 된다는 것을 보여 주는 문제다. sorted 는 원본을 안 바꾸고 새 리스트를 준다.

따라가기

  1. key=len → [kiwi, apple, banana] → kiwi
  2. 기본 → [apple, banana, kiwi] → apple
  3. 출력 kiwi apple

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 19번Pythonenumerate + 문자열 곱
r = '' for i, ch in enumerate("abc"): r += ch * i print(r)
정답bcc

— bcc. 첨자만큼 글자를 반복해 붙인다 — a 는 0번, b 는 1번, c 는 2번.

원리 — enumerate("abc") 는 (0, 'a'), (1, 'b'), (2, 'c') 를 차례로 준다 — 번호와 원소를 함께 받는 관용구다. ch * i 는 문자열 반복이라 'a' * 0 = '' (빈 문자열), 'b' * 1 = 'b', 'c' * 2 = 'cc'. r 에 차례로 붙이면 "" + "b" + "cc" = "bcc". 문자열에 정수를 곱하면 그 횟수만큼 반복된다는 파이썬 고유 문법이다.

따라가기

  1. (0, a) → '' · (1, b) → 'b'
  2. (2, c) → 'cc'
  3. 출력 bcc

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 20번Pythonzip의 길이 맞춤
a = [1, 2, 3] b = ['x', 'y'] print(list(zip(a, b)))
정답[(1, 'x'), (2, 'y')]

— [(1, 'x'), (2, 'y')]. zip 은 짧은 쪽에 맞춰 짝을 만들므로 3 은 짝이 없어 버려진다.

원리 — zip(a, b) 는 두 시퀀스를 같은 위치끼리 튜플로 묶는다 — (a[0], b[0]), (a[1], b[1]) …. 길이가 다르면 짧은 쪽이 끝나는 순간 멈춘다. a 는 3개, b 는 2개라 짝은 2개뿐이고 3 은 짝이 없어 빠진다. zip 은 게으른 객체라 list() 로 감싸야 내용이 보인다. 긴 쪽에 맞추려면 itertools.zip_longest 를 써야 한다.

따라가기

  1. (1, 'x'), (2, 'y')
  2. 3 은 짝 없음 → 제외
  3. 출력 [(1, 'x'), (2, 'y')]

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 21번Python예외 처리 흐름
try: print(10 // 0) except ZeroDivisionError: print("E", end='') finally: print("F")
정답EF

— EF. 0 으로 나누면 ZeroDivisionError 가 나 except 가 E 를, finally 가 F 를 같은 줄에 찍는다.

원리 — 10 // 0 은 계산 자체가 불가능해 ZeroDivisionError 예외가 발생하고 try 는 거기서 중단된다(print 에 닿지 못한다). except ZeroDivisionError 가 잡아 "E" 를 찍는데 end='' 라 줄바꿈이 없다. finally 는 항상 실행되어 "F" 를 찍고 기본 줄바꿈이 붙는다. 그래서 한 줄에 EF 다. 자바의 try-catch-finally 와 순서 규칙이 같다.

따라가기

  1. 10 // 0 → ZeroDivisionError
  2. except → E(줄바꿈 없음)
  3. finally → F. 출력 EF

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 22번Python음수 인덱스 + 슬라이스 역순
s = "SOFTWARE" print(s[-1] + s[0], s[2:5][::-1])
정답ES WTF

— ES WTF. 마지막 글자 E 와 첫 글자 S 를 붙이고, 2~4번 글자 FTW 를 뒤집었다.

원리 — "SOFTWARE" 에 번호를 붙이면 S0 O1 F2 T3 W4 A5 R6 E7 이다. s[-1] 은 마지막 E, s[0] 은 S, 문자열 + 는 이어 붙이기라 "ES". s[2:5] 는 2 · 3 · 4 = "FTW", 거기에 [::-1] 을 붙이면 뒤집혀 "WTF". 슬라이스 뒤에 또 슬라이스를 이어 쓸 수 있다는 것과 끝 번호 5 는 미포함이라는 것이 확인 지점이다.

따라가기

  1. s[-1] + s[0] → "E" + "S" → ES
  2. s[2:5] → "FTW" → [::-1] → WTF
  3. 출력 ES WTF

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 23번Pythonbool은 숫자다
print(True + True, int("3") + 4)
정답2 7

— 2 7. True 는 정수 1 로 계산되고, int("3") 은 문자열을 숫자 3 으로 바꾼다.

원리 — 파이썬의 bool 은 int 의 자식이라 True 는 1, False 는 0 으로 산술에 참여한다. True + True = 2. int("3") 은 문자열 "3" 을 정수 3 으로 변환하므로 3 + 4 = 7. 변환 없이 "3" + 4 를 하면 문자열과 정수를 더할 수 없어 TypeError 다 — 자바처럼 자동으로 붙여 주지 않는다. 형 변환 함수 int() · str() · float() 의 역할을 묻는 문제다.

따라가기

  1. True + True → 1 + 1 → 2
  2. int("3") + 4 → 3 + 4 → 7
  3. 출력 2 7

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 24번Pythonmax(key=) + values()
d = {'kim': 85, 'lee': 92, 'park': 78} print(max(d, key=d.get), min(d.values()))
정답lee 78

— lee 78. 값이 가장 큰 키는 lee(92), 값 중 최솟값은 78 이다.

원리 — max(d) 는 키 중 최대(사전 순 park)를 주지만, key=d.get 을 주면 각 키를 그 값으로 바꿔 비교하므로 값이 가장 큰 키 lee 가 나온다. d.values() 는 값들 85 · 92 · 78 이고 min 은 78. "값 기준으로 키 찾기"는 key=d.get, "값 자체"는 d.values() — 딕셔너리 최대 · 최소 문제의 두 관용구다.

따라가기

  1. max(d, key=d.get) → 92 의 키 → lee
  2. min(d.values()) → 78
  3. 출력 lee 78

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 25번Python중첩 리스트 인덱싱
m = [[1, 2], [3, 4], [5, 6]] print(m[1][0] + m[2][1])
정답9

— 9. m[1][0] = 3 과 m[2][1] = 6 을 더했다.

원리 — 리스트 안의 리스트는 2차원 표처럼 m[행][열] 로 접근한다. m[1] 은 두 번째 행 [3, 4] 이고 그 [0] 은 3. m[2] 는 세 번째 행 [5, 6] 이고 그 [1] 은 6. 합 9. 첨자는 0 부터라 m[1] 이 "1번째"가 아니라 "두 번째"다. 표를 그려 놓고 첨자가 가리키는 칸을 짚으면 틀리지 않는다. len(m) 은 행 수 3, len(m[0]) 은 열 수 2 다.

따라가기

  1. m[1][0] → [3, 4] 의 0번 → 3
  2. m[2][1] → [5, 6] 의 1번 → 6
  3. 3 + 6 = 9

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 26번Python문자열 곱 + in 연산
print("ab" * 2, "cd" in "abcde")
정답abab True

— abab True. 문자열 곱은 반복이고, in 은 부분 문자열 포함 여부다.

원리 — "ab" * 2 는 "ab" 를 두 번 이어 "abab" 다 — 문자열과 정수의 곱은 반복이다. "cd" in "abcde" 는 "cd" 라는 연속된 조각이 들어 있는지 보는 것이라 True. 글자 하나가 아니라 부분 문자열 전체가 연속으로 있어야 한다("ce" in "abcde" 는 False). 두 연산 다 파이썬 문자열에서 자주 쓰는 편의 문법이다.

따라가기

  1. "ab" * 2 → abab
  2. "cd" in "abcde" → True
  3. 출력 abab True

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 27번Pythonpop / insert 조합
a = [1, 2, 3, 4] a.pop() a.pop(0) a.insert(1, 9) print(a)
정답[2, 9, 3]

— [2, 9, 3]. 끝을 빼고, 앞을 빼고, 1번 자리에 9 를 끼웠다.

원리 — pop() 은 인자가 없으면 마지막 원소를 빼서 돌려준다 → [1, 2, 3]. pop(0) 은 0번 원소를 뺀다 → [2, 3]. insert(1, 9) 는 1번 자리에 9 를 끼워 넣고 뒤를 민다 → [2, 9, 3]. 세 메서드 모두 리스트 자체를 바꾼다(제자리). 한 줄씩 리스트를 적어 가는 것이 유일한 방법이다. remove(값) 은 값으로 지우고 pop(첨자) 는 자리로 지운다는 차이도 같이 나온다.

따라가기

  1. pop() → [1, 2, 3]
  2. pop(0) → [2, 3]
  3. insert(1, 9) → [2, 9, 3]

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 28번Pythonget의 기본값
d = {'a': 1} print(d.get('b', 0) + d.get('a', 0))
정답1

— 1. 없는 키 b 는 기본값 0, 있는 키 a 는 1 이라 합 1 이다.

원리 — d['b'] 처럼 없는 키를 대괄호로 읽으면 KeyError 가 나지만, d.get('b', 0) 은 없으면 두 번째 인자(기본값) 0 을 돌려준다. d.get('a', 0) 은 키가 있으니 그 값 1 이다. 0 + 1 = 1. get 은 "없을 수도 있는 키를 안전하게 읽는" 방법이고, 기본값을 생략하면 None 이 나온다 — 그러면 None + 1 은 오류다. 빈도 세기 코드 d[k] = d.get(k, 0) + 1 이 이 관용구의 대표다.

따라가기

  1. d.get('b', 0) → 없음 → 0
  2. d.get('a', 0) → 1
  3. 0 + 1 = 1

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 29번Pythonreversed + 자릿수 조립
r = 0 for i in reversed(range(3)): r = r * 10 + i print(r)
정답210

— 210. 2 · 1 · 0 을 차례로 한 자리씩 붙여 만든 수다.

원리 — reversed(range(3)) 은 2, 1, 0 순서다. r = r * 10 + i 는 "지금까지의 수를 한 자리 왼쪽으로 밀고 새 자리를 붙이는" 관용구다 — 0 → 0 × 10 + 2 = 2 → 2 × 10 + 1 = 21 → 21 × 10 + 0 = 210. 자릿수를 숫자로 조립할 때 쓰는 형이고, 반대로 % 10 과 // 10 으로 뜯는 코드(T-C-21)와 짝을 이룬다.

따라가기

  1. i=2 → r 2
  2. i=1 → r 21
  3. i=0 → r 210. 출력 210

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

훈련 Python · 30번Pythonlambda key 정렬
pairs = [(1, 3), (2, 1), (3, 2)] pairs.sort(key=lambda p: p[1]) print(pairs[0][0], pairs)
정답2 [(2, 1), (3, 2), (1, 3)]

— 2 [(2, 1), (3, 2), (1, 3)]. 각 튜플의 두 번째 값으로 정렬했다.

원리 — sort(key=lambda p: p[1]) 은 원소마다 p[1](두 번째 값)을 뽑아 그 순서로 정렬한다. (1, 3) 은 3, (2, 1) 은 1, (3, 2) 는 2 이므로 1 · 2 · 3 순으로 (2, 1), (3, 2), (1, 3) 이 된다. sort 는 제자리라 pairs 자체가 바뀐다. pairs[0] 은 (2, 1) 이고 그 [0] 은 2. key 없이 정렬했다면 첫 값 기준이라 원래 순서 그대로였다 — key 가 무엇을 뽑는지가 전부다.

따라가기

  1. p[1] 기준: 1 → (2, 1), 2 → (3, 2), 3 → (1, 3)
  2. pairs[0][0] → 2
  3. 출력 2 [(2, 1), (3, 2), (1, 3)]

이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 Python 3.12 실행 결과와 대조했다.

직접 돌려 보는 법

파이썬이 깔려 있으니 코드를 main.py 로 저장하고 파워셸에서 python main.py 를 치면 된다. 한 줄짜리는 python -c "print('abc'[::-1])" 처럼 바로 확인한다.

python -c "s = '_THIS_IS_KIM_SPEAKING'; print(s[:4] + s[6:8] + s[18:])"

위 명령은 이 노트를 만든 컴퓨터의 Python 3.12 에서 실행해 _THIISING 이 나온 것이다. 이 장의 기출 15개 · 훈련 30개도 같은 방법으로 확인했다.

07백지 재현

왜 이걸 하나 — 아래를 안 보고 적을 수 있어야 이 장이 끝난다.