목차
  1. 0. 왜 이만큼만 배우는가
  2. 1. 실행하고 출력하기
  3. 2. 변수·자료형·입력
  4. 3. 리스트와 딕셔너리
  5. 4. 조건문과 값 비교
  6. 5. 반복문과 2차원 순회
  7. 6. 함수
  8. 7. 예외 처리와 안전한 입력
  9. 8. 클래스
  10. 9. 파일과 JSON
  11. 10. 시간 측정과 표준 라이브러리
  12. 11. 미션 조립 가이드
Codyssey Mission 1–3

미션 파이썬

퀴즈 게임(미션 2)과 Mini NPU 시뮬레이터(미션 3)를 완주하는 데 필요한 최소한의 파이썬만 담은 교재입니다. 문법 백과가 아니라, 각 장을 읽고 나면 미션 요구사항 하나를 직접 구현할 수 있도록 구성했습니다.

Chapter 0

왜 이만큼만 배우는가

세 미션의 요구사항을 분석하면, 파이썬이 실제로 필요한 곳은 명확합니다.

  • 미션 1 (개발 워크스테이션): 터미널·Docker·Git 실습이 중심입니다. 웹 서버도 정적 HTML + NGINX로 충분해서 파이썬 문법이 거의 필요 없습니다. 이 책에서는 터미널에서 파이썬을 실행·확인하는 방법만 다룹니다.
  • 미션 2 (퀴즈 게임): 변수, 자료형, 조건문, 반복문, 함수, 클래스 2개, JSON 파일 저장(state.json), try/except 예외 처리가 전부 요구사항에 명시되어 있습니다.
  • 미션 3 (Mini NPU): 2차원 리스트, 중첩 반복문으로 MAC 연산, 부동소수점 epsilon 비교, data.json 로드와 키 파싱, time으로 성능 측정이 핵심입니다. 외부 라이브러리는 금지이므로 표준 문법만으로 해결해야 합니다.

따라서 이 책은 아래 매핑을 커리큘럼으로 삼습니다. 어떤 장을 왜 배우는지 항상 미션 요구사항으로 되짚을 수 있습니다.

장배우는 것직접 구현하게 되는 미션 요구사항
1실행, print, 주석M1 파이썬 설치 확인 · M2/M3 python main.py 실행
2변수, int/float/str/bool, input, 형변환M2 §3 입력 처리(공백 제거·숫자 변환) · M3 모드 1 숫자 파싱
3list, dict, 중첩 구조M2 퀴즈 목록/선택지 · M3 n×n 패턴·필터 저장
4if/elif/else, 범위 검사, epsilon 비교M2 메뉴 분기·정답 판정 · M3 점수 비교 정책(1e-9)
5for, while, 중첩 루프M2 메뉴 루프·퀴즈 출제 · M3 MAC 연산(반복문 직접 구현)
6함수, 매개변수, 반환값M2 기능별 함수 분리 · M3 연산/판정 함수
7try/except, KeyboardInterrupt, EOFErrorM2 §3 공통 예외 기준 · M3 케이스 단위 FAIL 처리
8class, __init__, self, 메서드M2 Quiz·QuizGame 클래스(필수 2개)
9open, with, json.load/dumpM2 state.json 저장/복구 · M3 data.json 로드·스키마 검증
10time.perf_counter, random, 문자열 메서드M3 10회 반복 평균 측정·O(N²) 표 · M2 보너스 랜덤 출제
11전체 조립M2·M3 프로그램 뼈대와 제출 체크리스트
Chapter 1

실행하고 출력하기

미션 1 · 환경 확인미션 2 · 실행 방법미션 3 · 실행 흐름

미션 2는 Python 3.10 이상, 미션 3은 3.8 이상을 요구합니다. 터미널에서 버전부터 확인합니다.

$ python3 --version
Python 3.12.4
$ python3 main.py        # 프로젝트 폴더에서 프로그램 실행

파이썬 파일(.py)은 위에서 아래로 한 줄씩 실행됩니다. 화면 출력은 print()가 담당합니다.

# main.py — # 뒤는 주석: 실행되지 않는 설명
print("=" * 40)          # 문자열 × 정수 = 반복 → 구분선을 손으로 치지 않는다
print("=== Mini NPU Simulator ===")
print("1. 사용자 입력 (3x3)")
print("2. data.json 분석")
print("-" * 40)

print()는 여러 값을 쉼표로 이어 출력할 수 있고, 자동으로 줄바꿈합니다. 미션 2의 메뉴 화면, 미션 3의 결과 표는 결국 print()의 나열입니다.

흔한 실수

맥에서는 python이 아니라 python3일 수 있습니다. 또 main.py가 있는 폴더로 cd한 뒤 실행해야 미션 3의 data.json 같은 상대 경로 파일을 제대로 찾습니다.

연습 1

미션 2 예시의 메뉴 화면(제목 줄, 1~5번 항목, 구분선)을 print()만으로 그대로 출력하는 menu.py를 만들어 실행해 보세요.

Chapter 2

변수·자료형·입력

미션 2 §3 · 공통 입력 기준미션 3 · 모드 1 입력 검증

변수: 값에 이름 붙이기

변수는 값을 담아두는 이름표입니다. 프로그램이 기억해야 하는 것 — 최고 점수, 현재 문제 번호, 필터 크기 — 은 전부 변수에 저장됩니다.

best_score = 0          # int   : 정수 (점수, 정답 번호, 크기 N)
score_a    = 0.9        # float : 실수 (MAC 점수는 float 가능)
question   = "Python의 창시자는?"   # str : 문자열
is_correct = True       # bool  : True / False 둘 중 하나

네 가지 타입의 차이를 설명할 수 있어야 합니다(미션 2 과제 목표에 명시).

  • int — 셀 수 있는 수. 메뉴 번호, 정답 번호(1~4), 최고 점수.
  • float — 소수점이 있는 수. 미션 3의 점수·시간(ms). 오차가 있을 수 있음(4장에서 다룸).
  • str — 글자들. 문제 텍스트, 파일에서 읽은 내용, input()의 결과는 항상 str.
  • bool — 참/거짓. 판정 결과, 반복 계속 여부.

f-string: 문자열 안에 값 끼워 넣기

total, correct = 5, 4
print(f"🏆 결과: {total}문제 중 {correct}문제 정답! ({correct * 20}점)")
print(f"평균 시간: {0.0123456:.3f} ms")   # :.3f → 소수점 3자리
🏆 결과: 5문제 중 4문제 정답! (80점)
평균 시간: 0.012 ms

미션 3의 성능 표(소수점 자리 맞춤)와 미션 2의 결과 화면은 f-string 하나로 해결됩니다.

input(): 사용자 입력 받기

input()은 안내 문구를 출력하고, 사용자가 Enter를 칠 때까지 기다렸다가 항상 문자열(str)을 돌려줍니다. 숫자로 쓰려면 변환이 필요합니다.

raw = input("선택: ")      # 사용자가 " 1 " 입력했다면 raw는 " 1 "
raw = raw.strip()          # 앞뒤 공백 제거 → "1"  (미션 2 필수 요구)
choice = int(raw)          # 문자열 → 정수. "abc"면 ValueError 발생(7장)

한 줄에서 숫자 여러 개 읽기 — 미션 3의 행 입력

미션 3 모드 1은 "한 줄에 숫자 3개를 공백으로 구분해" 입력받습니다. split()이 문자열을 공백 기준으로 쪼개 리스트로 만들어 줍니다.

line = input().strip()     # "0 1 0"
parts = line.split()       # ["0", "1", "0"]  ← 아직 문자열
row = [float(p) for p in parts]   # [0.0, 1.0, 0.0] ← 숫자로 변환
print(len(row))            # 3 — 열 개수 검증에 사용

마지막 줄의 [float(p) for p in parts]는 리스트 컴프리헨션으로, "parts의 각 항목 p를 float(p)로 바꾼 새 리스트"라는 뜻입니다. 5장의 for문을 한 줄로 줄인 표현이라, 지금은 관용구로 외워 두어도 됩니다.

연습 2

한 줄을 입력받아 ① 공백을 제거하고 ② 공백 기준으로 쪼갠 뒤 ③ 숫자 개수가 3개인지 len()으로 확인해 "OK" 또는 "입력 형식 오류: 각 줄에 3개의 숫자를 공백으로 구분해 입력하세요."를 출력하는 프로그램을 작성하세요. 미션 3의 입력 검증 문구 그대로입니다.

Chapter 3

리스트와 딕셔너리

미션 2 · 퀴즈 목록·선택지미션 3 §1 · n×n 데이터 구조

리스트: 순서가 있는 묶음

choices = ["Guido", "Linus", "Bjarne", "James"]
choices[0]        # "Guido"   — 인덱스는 0부터!
choices[3]        # "James"
len(choices)      # 4
choices.append("Anders")   # 끝에 추가 → 퀴즈 추가 기능의 핵심
choices.pop(1)             # 인덱스 1 항목을 꺼내며 제거 → 퀴즈 삭제 보너스
del choices[0]             # 그냥 지우기만 할 때

삭제 후에는 뒤 항목들의 인덱스가 하나씩 당겨진다는 점만 주의하세요. 미션 2 보너스 4(퀴즈 삭제)는 사용자에게 번호를 받아 quizzes.pop(번호 - 1) 하고 파일에 다시 저장하면 됩니다.

흔한 실수 — 인덱스와 정답 번호

미션 2에서 정답은 1~4번으로 관리하지만 리스트 인덱스는 0~3입니다. 사용자가 3을 입력하면 choices[3 - 1]이 해당 선택지입니다. 이 -1을 빠뜨리는 것이 퀴즈 게임에서 가장 흔한 버그입니다.

딕셔너리: 이름표가 붙은 묶음

딕셔너리(dict)는 키 → 값 쌍의 모음입니다. JSON과 구조가 같아서, 9장에서 파일 저장을 배우면 dict가 그대로 state.json이 됩니다.

quiz = {
    "question": "Python의 창시자는?",
    "choices": ["Guido", "Linus", "Bjarne", "James"],
    "answer": 1,
}
quiz["question"]          # 키로 값 꺼내기
quiz["answer"] = 2        # 값 바꾸기
quiz.get("hint")          # 없는 키 → None (오류 대신). 검증에 유용
"answer" in quiz         # True — 키 존재 확인 (스키마 검증)

None은 "값이 없음"을 나타내는 특별한 값입니다. get()이 없는 키에 대해 돌려주는 것이 바로 None이고, 검사할 때는 == 대신 if x is None:을 쓰는 것이 관례입니다. 9장에서 data["filters"].get(f"size_{n}")의 결과가 None인지 확인해 스키마 오류를 걸러낼 때 그대로 사용합니다.

중첩 구조 ① — 딕셔너리의 리스트 (미션 2)

퀴즈 여러 개는 "dict를 담은 list"입니다. state.json의 quizzes가 정확히 이 모양입니다.

quizzes = [
    {"question": "...", "choices": [...], "answer": 3},
    {"question": "...", "choices": [...], "answer": 2},
]
quizzes[0]["question"]    # 첫 퀴즈의 문제 텍스트

중첩 구조 ② — 리스트의 리스트 = 2차원 배열 (미션 3)

미션 3의 패턴과 필터는 "행(row)들의 리스트"로 표현합니다. pattern[i][j]는 i행 j열의 값입니다.

cross = [            # 3×3 십자가 필터
    [0, 1, 0],
    [1, 1, 1],
    [0, 1, 0],
]
cross[1][2]          # 1행 2열 → 1
len(cross)           # 행 수 = 3
len(cross[0])        # 열 수 = 3 → "필터와 패턴 크기 일치" 검증에 사용
연습 3

① 퀴즈 dict 2개를 담은 quizzes 리스트를 만들고, 두 번째 퀴즈의 3번 선택지를 출력하세요. ② 3×3 X 패턴을 리스트의 리스트로 만들고 가운데 값([1][1])을 출력하세요.

Chapter 4

조건문과 값 비교

미션 2 · 메뉴 분기·정답 판정미션 3 §6 · epsilon 비교 정책

if / elif / else

메뉴에서 번호에 따라 다른 기능을 실행하는 것이 조건문의 전형적인 쓰임입니다.

if choice == 1:
    play_quiz()
elif choice == 2:
    add_quiz()
elif choice == 5:
    print("저장 후 종료합니다.")
else:
    print("⚠️ 잘못된 입력입니다. 1-5 사이의 숫자를 입력하세요.")

파이썬은 들여쓰기(스페이스 4칸)가 블록을 결정합니다. 콜론(:) 다음 줄부터 들여 쓴 부분까지가 해당 조건에 속합니다.

비교·논리 연산자와 범위 검사

answer == 3        # 같다 (= 하나는 대입, 두 개가 비교!)
answer != 3        # 다르다
1 <= n <= 4        # 범위 검사 — "허용 범위 밖 숫자" 걸러내기
raw == ""          # 빈 입력 검사
a > b and c > d    # 둘 다 참일 때
not quizzes        # 리스트가 비었으면 True → "퀴즈가 없는 경우" 처리

부동소수점은 == 로 비교하면 안 된다

float는 이진 소수라서 미세한 오차를 갖습니다. 미션 3이 epsilon 정책을 필수로 요구하는 이유입니다.

print(0.1 + 0.2)          0.30000000000000004
print(0.1 + 0.2 == 0.3)   False   ← 함정!

그래서 "두 점수가 같다(동점)"는 차이가 아주 작은 허용오차(epsilon)보다 작다로 정의합니다.

EPSILON = 1e-9            # 0.000000001 — 상수는 대문자 이름 관례

if abs(score_a - score_b) < EPSILON:
    verdict = "UNDECIDED"       # 동점 → 판정 불가
elif score_a > score_b:
    verdict = "A"
else:
    verdict = "B"
연습 4

score_a = 0.9, score_b = 0.8999999999999999일 때 위 epsilon 판정 코드가 무엇을 출력하는지 예측한 뒤 실행해 확인하세요. 그다음 EPSILON을 1e-20으로 바꾸면 결과가 왜 달라지는지 설명해 보세요.

Chapter 5

반복문과 2차원 순회

미션 2 · 메뉴 루프·퀴즈 출제미션 3 §5 · MAC 연산 직접 구현

for: 정해진 목록을 순회

for quiz in quizzes:              # 퀴즈를 하나씩 꺼내며
    print(quiz["question"])

for i in range(4):               # i = 0, 1, 2, 3
    print(f"{i + 1}. {quiz['choices'][i]}")

for idx, quiz in enumerate(quizzes, start=1):   # 번호와 함께
    print(f"[{idx}] {quiz['question']}")          # → 퀴즈 목록 화면

while: 조건이 참인 동안 반복

"종료를 선택할 때까지 메뉴를 계속 보여준다"는 while True + break가 표준 패턴입니다. for는 횟수·목록이 정해졌을 때, while은 언제 끝날지 사용자에게 달렸을 때 씁니다 — 이 구분이 미션 2 과제 목표 중 하나입니다.

while True:
    show_menu()
    choice = input("선택: ").strip()
    if choice == "5":
        break                # 반복 탈출 → 프로그램 종료 수순
    if choice not in ("1", "2", "3", "4"):
        print("⚠️ 잘못된 입력입니다. 1-5 사이의 숫자를 입력하세요.")
        continue             # 아래를 건너뛰고 다음 반복(재입력)으로
    ...

중첩 for — 2차원을 훑는 법, 그리고 MAC 연산

바깥 for가 행을, 안쪽 for가 열을 돕니다. 미션 3의 핵심인 MAC(곱하고 누적해서 더하기) 연산은 중첩 for 안에서 곱을 누적하는 것이 전부입니다.

def mac(pattern, filt):
    """같은 위치끼리 곱해 모두 더한 점수를 반환 (외부 라이브러리 금지 → 직접 구현)"""
    n = len(pattern)
    total = 0.0                      # 누적(Accumulate)용 변수
    for i in range(n):               # 행
        for j in range(n):           # 열
            total += pattern[i][j] * filt[i][j]   # 곱하고(Multiply) 더한다
    return total

score = mac([[0,1,0],[1,1,1],[0,1,0]],
            [[0,1,0],[1,1,1],[0,1,0]])
print(score)     5.0  ← 미션 문서의 Case 1과 동일

N×N 입력이면 곱셈이 N² 번 실행됩니다. 3×3은 9번, 25×25는 625번 — 이것이 미션 3 리포트에 쓸 O(N²)의 근거입니다. 코드에서 직접 세어 보여줄 수 있으니 리포트 작성이 쉬워집니다.

중첩 컴프리헨션과 평탄화 — 2차원을 1차원으로

2장의 [float(p) for p in parts]는 for절이 하나인 기본형이었습니다. for절은 여러 개 이어 쓸 수 있고, 읽는 규칙은 하나입니다: for절들은 왼쪽부터 순서대로 중첩 for문이 되고, 맨 앞의 표현식이 가장 안쪽에 들어간다.

flat = [value for row in matrix for value in row]

# 위 한 줄은 아래 중첩 for문과 완전히 같다
flat = []
for row in matrix:          # ① 첫 번째 for절 — 바깥 루프
    for value in row:       # ② 두 번째 for절 — 안쪽 루프
        flat.append(value)  # ③ 맨 앞의 표현식 — 가장 안쪽

3×3 십자가에 적용하면 [0,1,0,1,1,1,0,1,0] — 2차원 리스트가 길이 N²의 1차원 리스트로 평탄화(flatten)됩니다. 이때 (i, j) 위치는 flat[i * n + j]에 대응합니다.

흔한 실수 — for절 순서

[value for value in row for row in matrix]처럼 순서를 뒤집으면 첫 for절이 아직 정의되지 않은 row를 쓰게 되어 NameError가 납니다. 앞의 for절이 만든 변수를 뒤의 for절이 쓴다(바깥→안 순서)고 기억하세요.

연습 5

위 mac()에 미션 문서의 Case 2(십자가 입력 × X 필터)를 넣어 1.0이 나오는지 확인하세요. 이어서 크기 N을 입력받아 N×N 십자가 패턴(가운데 행·열이 1)을 중첩 for로 자동 생성해 보세요 — 미션 3 보너스 과제 2의 절반이 끝납니다.

Chapter 6

함수

미션 2 §7-2 · 기능별 함수 분리미션 3 · 연산/판정/측정 함수

미션 2 제약 사항은 "모든 코드를 한 함수에 작성하지 말고 기능별로 분리"를 요구합니다. 함수는 입력(매개변수)을 받아 → 일을 하고 → 결과(반환값)를 돌려주는 이름 붙은 코드 블록입니다.

def calc_score(correct, total):        # correct, total = 매개변수
    if total == 0:
        return 0                       # return을 만나면 즉시 종료
    return round(correct / total * 100)

score = calc_score(4, 5)               # 호출 → 반환값 80을 score에 저장

값을 여러 개 돌려주기

미션 3에서는 "점수와 소요 시간"처럼 결과가 여럿인 경우가 많습니다. 쉼표로 묶어 반환하고, 받을 때도 쉼표로 풀면 됩니다.

def judge(score_cross, score_x, eps=1e-9):   # eps는 기본값 있는 매개변수
    if abs(score_cross - score_x) < eps:
        return "UNDECIDED", False
    if score_cross > score_x:
        return "Cross", True
    return "X", True

verdict, decided = judge(5.0, 1.0)          # "Cross", True

함수로 나누는 기준

  • 한 함수는 한 가지 일만: show_menu(), play_quiz(), load_state(), mac(), measure()…
  • 화면 출력과 계산을 분리하면 테스트와 디버깅이 쉬워집니다. mac()은 print 없이 값만 반환하고, 출력은 호출한 쪽이 담당하게 하세요. 미션 3의 "시간 측정은 I/O를 제외하고 연산 함수 구간 중심으로" 요구가 자연스럽게 충족됩니다.
  • 같은 코드를 두 번 쓰게 되면 함수로 뽑을 신호입니다(예: 3×3 필터 A·B 입력 → read_matrix(3) 하나로).
연습 6

2장 연습의 "한 줄 검증"을 확장해, 크기 n을 받아 n줄을 입력받고 2차원 리스트를 반환하는 read_matrix(n) 함수를 작성하세요(형식 오류 시 재입력은 다음 장에서 붙입니다). 이 함수 하나가 미션 3 모드 1의 필터 A·B·패턴 입력 세 곳에 재사용됩니다.

Chapter 7

예외 처리와 안전한 입력

미션 2 §3 · 공통 입력/예외 기준미션 3 · 재입력 유도·케이스 FAIL

int("abc")는 ValueError라는 예외를 일으키며 프로그램을 즉사시킵니다. try/except는 "일단 시도하고, 사고가 나면 이 코드로 수습하라"는 구조입니다.

try:
    n = int("abc")        # 여기서 ValueError 발생 →
except ValueError:
    print("숫자가 아닙니다.")   # → 죽지 않고 여기로 점프

표준 패턴: 유효한 숫자를 받을 때까지 묻는 함수

미션 2 §3의 네 가지 요구(공백 제거, 변환 실패, 범위 밖, 빈 입력)를 전부 처리하는 함수입니다. 이 함수 하나로 메뉴 선택·정답 입력·정답 번호 입력을 모두 해결합니다.

def read_int(prompt, lo, hi):
    """lo~hi 범위의 정수를 받을 때까지 재입력을 유도한다."""
    while True:
        raw = input(prompt).strip()          # ① 공백 제거
        if raw == "":                        # ② 빈 입력
            print(f"⚠️ 입력이 비었습니다. {lo}-{hi} 숫자를 입력하세요.")
            continue
        try:
            n = int(raw)                     # ③ 변환 실패 대비
        except ValueError:
            print(f"⚠️ 잘못된 입력입니다. {lo}-{hi} 사이의 숫자를 입력하세요.")
            continue
        if not (lo <= n <= hi):              # ④ 범위 밖
            print(f"⚠️ 잘못된 입력입니다. {lo}-{hi} 사이의 숫자를 입력하세요.")
            continue
        return n                             # 유효하면 반환하고 끝

choice = read_int("선택: ", 1, 5)
answer = read_int("정답 입력: ", 1, 4)

같은 패턴의 변형 — 텍스트 입력과 행렬 입력

"유효할 때까지 while True로 되묻는다"는 뼈대는 어떤 입력에도 재사용됩니다. 미션 2의 퀴즈 추가(문제·선택지는 빈 문자열만 아니면 됨)와 미션 3 모드 1의 행 입력(6장 연습에서 미뤄 둔 재입력 유도)이 각각 이렇게 완성됩니다.

def read_text(prompt):
    """빈 입력이 아닐 때까지 묻는다 — 퀴즈 문제/선택지 입력용."""
    while True:
        raw = input(prompt).strip()
        if raw:                     # 비어 있지 않으면 참
            return raw
        print("⚠️ 입력이 비었습니다. 다시 입력하세요.")

def read_row(n):
    """숫자 n개가 든 한 행을 받을 때까지 재입력을 유도한다."""
    while True:
        parts = input().strip().split()
        if len(parts) != n:          # 열 개수 검증
            print(f"입력 형식 오류: 각 줄에 {n}개의 숫자를 공백으로 구분해 입력하세요.")
            continue
        try:
            return [float(p) for p in parts]
        except ValueError:           # 숫자 파싱 실패
            print("입력 형식 오류: 숫자만 입력하세요.")

def read_matrix(n):
    """n×n 행렬 입력 — 필터 A·B·패턴 세 곳에서 재사용."""
    return [read_row(n) for _ in range(n)]

이 세 함수로 미션 3 §2 모드 1 입력 검증(행/열 개수, 숫자 파싱, 재입력 유도)이 전부 충족됩니다. read_text()는 미션 2 퀴즈 추가 화면의 문제·선택지 4개 입력에 그대로 씁니다.

Ctrl+C에도 비정상 종료하지 않기

실행 중 Ctrl+C는 KeyboardInterrupt, 입력 스트림이 끊기면 EOFError 예외가 됩니다. 미션 2는 이때도 "저장 후 안전하게 종료"를 요구합니다. 프로그램의 최상위를 try로 감싸는 것이 정석입니다.

def main():
    game = QuizGame()          # 8장에서 배울 클래스
    try:
        game.run()             # 메뉴 루프 전체
    except (KeyboardInterrupt, EOFError):
        print("\n입력이 중단되어 저장 후 종료합니다.")
        game.save()            # 가능한 범위에서 저장

if __name__ == "__main__":     # "직접 실행했을 때만 main()을 호출" 관용구
    main()
연습 7

read_int()를 직접 타이핑해 실행하고 abc, 9, 빈 Enter,  3 (공백 포함) 네 가지를 차례로 넣어 모두 의도대로 동작하는지 확인하세요. 그대로 미션 2 제출 코드에 쓸 수 있습니다.

Chapter 8

클래스

미션 2 §4·§10 · Quiz / QuizGame 필수

미션 2는 클래스 2개 이상을 요구합니다. 클래스는 "관련된 데이터(속성)와 그 데이터를 다루는 함수(메서드)를 한 덩어리로 묶는 설계도"입니다. 퀴즈 한 문제가 가진 것(문제·선택지·정답)과 할 수 있는 일(출력·채점)을 묶으면 Quiz 클래스가 됩니다.

class Quiz:
    def __init__(self, question, choices, answer):
        """인스턴스가 만들어질 때 자동 호출되는 초기화 메서드"""
        self.question = question    # 속성(attribute)에 저장
        self.choices = choices      # 선택지 4개 리스트
        self.answer = answer        # 정답 번호 1~4

    def show(self, number):
        """문제와 선택지를 출력한다."""
        print(f"[문제 {number}] {self.question}")
        for i, choice in enumerate(self.choices, start=1):
            print(f"  {i}. {choice}")

    def check(self, user_answer):
        """정답이면 True를 반환한다."""
        return user_answer == self.answer

설계도(클래스)에서 실제 물건(인스턴스)을 찍어내는 것이 호출입니다.

q = Quiz("Python의 창시자는?", ["Guido","Linus","Bjarne","James"], 1)
q.show(1)          # 메서드 호출 — self에는 q가 자동으로 들어간다
q.check(1)         # True
self가 어렵다면

self는 "지금 이 메서드를 호출한 바로 그 인스턴스 자신"입니다. q.check(1)은 사실 Quiz.check(q, 1)과 같습니다. 그래서 모든 메서드의 첫 매개변수는 self이고, 호출할 때는 적지 않습니다. __init__ 안에서 self.question = question처럼 self에 붙여 저장해야 다른 메서드에서도 꺼내 쓸 수 있습니다.

QuizGame: 게임 전체를 관리하는 클래스

두 번째 클래스는 게임의 상태(퀴즈 목록, 최고 점수)와 흐름(메뉴, 저장/불러오기)을 담당합니다. 역할 분담: Quiz는 문제 한 개, QuizGame은 게임 전체.

class QuizGame:
    def __init__(self):
        self.quizzes = []           # Quiz 인스턴스들의 리스트
        self.best_score = 0
        self.load()                 # 시작하자마자 state.json 불러오기(9장)

    def run(self):
        while True:
            self.show_menu()
            choice = read_int("선택: ", 1, 5)
            if choice == 1: self.play()
            elif choice == 2: self.add_quiz()
            elif choice == 3: self.show_list()
            elif choice == 4: self.show_score()
            else:
                self.save()
                print("저장 후 종료합니다.")
                break

play(), add_quiz(), show_list(), show_score(), save(), load()는 각각 미션 2 기능 요구사항 6~11번과 1:1로 대응합니다. 이 뼈대를 채우는 것이 곧 미션 완수입니다.

연습 8

Quiz 클래스를 완성하고, 인스턴스 2개를 리스트에 담아 for문으로 show() → read_int()로 답 입력 → check() 결과에 따라 "✅ 정답입니다!" / "❌ 오답입니다!"를 출력하는 미니 게임을 만들어 보세요. 이것이 play() 메서드의 본체입니다.

Chapter 9

파일과 JSON

미션 2 §11 · state.json 저장/복구미션 3 §3 · data.json 로드·검증

프로그램을 꺼도 데이터가 남으려면 파일에 써야 합니다. JSON은 dict/list를 텍스트로 저장하는 표준 형식이라, 파이썬 자료구조와 1:1로 오갑니다.

쓰기: dict → state.json

import json          # 표준 라이브러리 — 파일 맨 위에서 가져온다

state = {
    "quizzes": [{"question": "...", "choices": [...], "answer": 1}],
    "best_score": 80,
}
with open("state.json", "w", encoding="utf-8") as f:
    json.dump(state, f, ensure_ascii=False, indent=2)
  • with open(...) as f: — 블록이 끝나면 파일을 자동으로 닫아 줍니다. 항상 이 형태를 쓰세요.
  • encoding="utf-8" — 미션 2 요구사항(한글 깨짐 방지).
  • ensure_ascii=False — 한글을 안 같은 코드가 아니라 읽을 수 있는 글자로 저장.

읽기: 파일이 없거나 깨졌을 때까지 대비

미션 2는 "파일 없음 → 기본 데이터 사용, 손상 → 안내 후 복구"를 요구합니다. 예외 타입별로 나눠 받으면 됩니다.

def load_state():
    try:
        with open("state.json", "r", encoding="utf-8") as f:
            return json.load(f)              # 텍스트 → dict
    except FileNotFoundError:                # 첫 실행: 파일이 없다
        return {"quizzes": DEFAULT_QUIZZES, "best_score": 0}
    except json.JSONDecodeError:             # 파일이 깨졌다
        print("⚠️ 저장 파일이 손상되어 기본 데이터로 시작합니다.")
        return {"quizzes": DEFAULT_QUIZZES, "best_score": 0}
클래스 ↔ JSON 변환

JSON은 dict/list만 저장할 수 있고 Quiz 인스턴스는 모릅니다. 저장 시 {"question": q.question, "choices": q.choices, "answer": q.answer}로 풀고, 불러올 때 Quiz(d["question"], d["choices"], d["answer"])로 되살리세요. Quiz에 to_dict() 메서드, QuizGame.load()에 복원 코드를 두면 깔끔합니다.

미션 3: data.json 로드와 키 파싱

data.json은 filters(size_5·size_13·size_25)와 patterns(size_{N}_{idx} 키)로 구성됩니다. 딕셔너리 순회와 문자열 split()으로 키에서 N을 뽑아 맞는 필터를 고릅니다.

with open("data.json", "r", encoding="utf-8") as f:
    data = json.load(f)

for name, case in data["patterns"].items():   # 키와 값을 함께 순회
    n = int(name.split("_")[1])       # "size_13_1" → ["size","13","1"] → 13
    filters = data["filters"].get(f"size_{n}")
    pattern = case["input"]
    if filters is None or len(pattern) != n:      # 크기/스키마 검증
        print(f"{name}: FAIL (필터/크기 불일치)")   # 케이스만 FAIL, 계속 진행
        continue
    ...

라벨 정규화도 dict 하나로 끝납니다. 요구사항의 "‘+’→Cross, ‘x’→X, ‘cross’→Cross"를 매핑 테이블로 만들면 if 사다리가 필요 없습니다.

LABELS = {"+": "Cross", "cross": "Cross", "x": "X", "X": "X"}
expected = LABELS.get(str(case["expected"]).strip().lower(), None)
연습 9

① dict 하나를 test.json으로 저장했다가 다시 읽어 같은 내용인지 확인하세요. ② 파일을 텍스트 편집기로 일부러 망가뜨린 뒤(중괄호 삭제) load_state()가 안내 메시지와 함께 기본값을 돌려주는지 확인하세요. 이 실험이 미션 2 §11의 채점 시나리오입니다.

Chapter 10

시간 측정과 표준 라이브러리

미션 3 §6-2 · 성능 분석(10회 평균)미션 2 보너스 · 랜덤 출제

time.perf_counter — 연산 시간 재기

미션 3은 "각 크기별 MAC 연산을 10회 반복 측정해 평균(ms)"을 요구합니다. time.perf_counter()는 고해상도 시계로, 두 번 읽어 차이를 구하면 경과 시간(초)입니다.

import time

def measure_ms(pattern, filt, repeat=10):
    """MAC 연산만 repeat회 반복해 평균 소요 시간(ms)을 반환."""
    start = time.perf_counter()
    for _ in range(repeat):        # _ : 값을 안 쓰는 반복 변수 관례
        mac(pattern, filt)          # 측정 구간에는 연산만! (I/O 제외)
    elapsed = time.perf_counter() - start
    return elapsed / repeat * 1000  # 초 → ms, 평균

성능 표 출력은 f-string 정렬 지시자로 맞춥니다. {n:>2}는 오른쪽 정렬 2칸, {ms:>10.3f}는 10칸에 소수점 3자리.

print(f"{'크기':<8}{'평균 시간(ms)':>14}{'연산 횟수':>10}")
for n, ms in results:
    print(f"{n}×{n:<5}{ms:>14.3f}{n * n:>10}")

random — 미션 2 보너스

import random
random.shuffle(quizzes)          # 리스트를 제자리에서 섞기 → 랜덤 출제
random.sample(quizzes, 3)        # 3개만 뽑기 → 문제 수 선택 보너스

datetime — 날짜/시간 기록 (미션 2 보너스 5)

점수 기록 히스토리 보너스는 "날짜/시간, 푼 문제 수, 점수"를 저장해야 합니다. 표준 라이브러리 datetime으로 현재 시각을 문자열로 만들면 JSON에 그대로 저장할 수 있습니다.

from datetime import datetime

stamp = datetime.now().strftime("%Y-%m-%d %H:%M")   # "2026-08-13 14:02"
history.append({"when": stamp, "total": 5, "score": 80})
# history 리스트를 state.json에 함께 저장하면 보너스 5 완성 (9장 참고)

자주 쓰는 문자열·내장 도구 요약

도구하는 일미션에서의 쓰임
s.strip()앞뒤 공백 제거모든 입력 전처리
s.split("_")구분자로 쪼개 리스트로size_13_1 키 파싱, 행 입력
s.lower()소문자로라벨 정규화(x/X)
abs(x)절댓값epsilon 비교
len(x)길이/개수행·열 검증, 퀴즈 개수
round(x, 1)반올림점수 표시
enumerate(xs, 1)번호와 함께 순회목록·선택지 출력
d.items() / d.get(k)dict 순회 / 안전 조회patterns 순회, 스키마 검증
연습 10

5장에서 만든 십자가 패턴 생성기를 이용해 N = 3, 5, 13, 25 각각의 패턴·필터를 만들고, measure_ms()로 측정한 결과를 위 표 형식으로 출력해 보세요. 완성되면 미션 3 §6-2 성능 분석 요구사항이 그대로 끝납니다.

Chapter 11

미션 조립 가이드

미션 2 · 전체 구조미션 3 · 전체 구조

배운 조각을 어떤 순서로 붙이면 되는지, 두 미션의 권장 구조와 작업 순서입니다. 코드를 베끼는 게 아니라 각 단계에서 어느 장을 다시 펴면 되는지를 안내합니다.

미션 2 — 퀴즈 게임 조립 순서

  1. 뼈대: read_int()(7장) + 메뉴 while 루프(5장)만으로 "메뉴 → 잘못된 입력 처리 → 종료"가 도는 프로그램을 먼저 완성 → 커밋.
  2. Quiz 클래스(8장) + 기본 퀴즈 5개를 코드에 정의 → 커밋.
  3. 퀴즈 풀기: 브랜치를 만들어(git checkout -b feature/play) for 루프로 출제·채점(5·8장), 끝나면 점수 계산(6장) → 커밋 후 main에 병합.
  4. 추가/목록/점수: append(3장), enumerate(5장), 최고 점수 갱신 비교(4장) → 기능마다 커밋.
  5. 저장/불러오기: state.json 입출력(9장), Ctrl+C 안전 종료(7장) → 커밋.
  6. QuizGame으로 정리(8장) → README 작성 → 커밋 · 푸시, clone/pull 실습.

이 순서 자체가 "최소 10개의 의미 있는 커밋"과 브랜치·병합 요구를 자연스럽게 채워 줍니다.

미션 3 — Mini NPU 조립 순서

  1. mac() 함수(5장)를 만들고 미션 문서의 Case 1·2로 검증 — 5.0과 1.0이 나와야 합니다.
  2. read_matrix(n)(2·6·7장)으로 모드 1 입력 + 형식 오류 재입력.
  3. epsilon 판정 judge()(4장) + 결과 출력.
  4. data.json 로드, 키 파싱, 크기 검증, 라벨 정규화(9장). 오류 케이스는 FAIL 기록 후 continue.
  5. measure_ms()(10장)로 3·5·13·25 성능 표 출력.
  6. 총 테스트/통과/실패 요약(카운터 변수 + f-string), README 리포트 작성(O(N²) 근거는 10장).

제출 전 파이썬 체크리스트

  • 모든 input() 뒤에 .strip()이 있는가? 빈 입력·문자·범위 밖을 넣어도 안 죽는가?
  • Ctrl+C를 눌러도 안내 후 종료되는가? (M2: 저장까지 되는가?)
  • state.json을 지우고 실행 / 일부러 깨뜨리고 실행 — 둘 다 살아남는가?
  • float 비교에 ==를 쓴 곳이 없는가? (M3: epsilon 정책)
  • 클래스가 2개 이상이고, 한 함수가 50줄을 넘지 않는가?
  • 외부 라이브러리 import가 없는가? (json, time, random은 표준이라 허용)