4~6주차 — 표준 라이브러리 · 예외 처리 · 파일 입출력
추가 설치 없이 import만으로 쓸 수 있는 4가지 핵심 표준 모듈 — 날짜·시간(datetime), 무작위(random), 경로(pathlib), Counter/defaultdict(collections). try/except의 4개 절(try·except·else·finally), 여러 예외 분기 처리, raise로 의도적 발생, Exception 상속으로 사용자 정의 예외, 예외 체인을 익힙니다. with open 으로 안전하게 텍스트 파일 읽고 쓰기, csv 모듈로 표 형식 데이터 처리, json 으로 구조화 데이터 직렬화·역직렬화를 익힙니다.
이 강의에서 배우는 것
- 1datetime 으로 날짜·시간을 다룬다
- 2random 으로 무작위 값·샘플링
- 3pathlib 으로 파일·폴더 경로
- 4collections 의 Counter, defaultdict
- 5try/except/else/finally 의 각 절을 안다
- 6예외 종류를 구분해 처리한다
- 7사용자 정의 예외를 만든다
- 8예외를 의도적으로 발생시킨다(raise)
- 9텍스트 파일을 with open 으로 안전하게 다룬다
- 10CSV 파일을 csv 모듈로 읽고 쓴다
- 11JSON으로 구조화된 데이터를 저장·로드한다
4주차 — 표준 라이브러리
추가 설치 없이 import만으로 쓸 수 있는 4가지 핵심 표준 모듈 — 날짜·시간(datetime), 무작위(random), 경로(pathlib), Counter/defaultdict(collections).
1. datetime
from datetime import datetime, date, timedelta
now = datetime.now()
print(now)
print(now.strftime("%Y-%m-%d %H:%M"))
today = date.today()
birthday = date(2000, 5, 9)
days_lived = (today - birthday).days
next_week = now + timedelta(days=7)2. random
import random
print(random.random()) # 0.0 ~ 1.0
print(random.randint(1, 100)) # 1 ~ 100 정수
print(random.choice(["가위","바위","보"]))
print(random.sample(range(1, 46), 6)) # 로또
random.shuffle(my_list) # 제자리 섞기3. pathlib
문자열보다 깔끔한 경로 다루기.
from pathlib import Path
p = Path("data") / "report.txt" # data/report.txt
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello", encoding="utf-8")
print(p.read_text(encoding="utf-8"))
print(p.exists(), p.suffix, p.name, p.stem)
# 폴더 내 파일 목록
for f in Path(".").glob("*.py"):
print(f.name, f.stat().st_size)4. collections
from collections import Counter, defaultdict, deque
# Counter — 빈도 계산 한 줄
text = "to be or not to be"
print(Counter(text.split())) # {'to': 2, 'be': 2, ...}
print(Counter(text.split()).most_common(2))
# defaultdict — 키가 없으면 기본값
groups = defaultdict(list)
for name, age in [("A", 20), ("B", 25), ("C", 20)]:
groups[age].append(name)
print(dict(groups))
# deque — 양방향 큐
dq = deque([1, 2, 3])
dq.appendleft(0)
dq.append(4)
print(dq)5. 자주 하는 실수
- datetime vs date — datetime 은 시간까지, date 는 날짜만
- random.seed() 모름 — 같은 결과 재현하려면 random.seed(42) 처럼 시드 설정
- 경로 문자열 더하기 — "data" + "/" + "x" 보다 Path("data") / "x" 가 안전
6. FAQ
Q1. time 과 datetime 의 차이?
time 은 저수준(타임스탬프), datetime 은 고수준(객체). 보통 datetime 이면 충분.
Q2. os.path 와 pathlib 중 뭐?
신규 코드는 pathlib 권장. 더 OOP스럽고 가독성 좋음.
5주차 — 예외 처리
try/except의 4개 절(try·except·else·finally), 여러 예외 분기 처리, raise로 의도적 발생, Exception 상속으로 사용자 정의 예외, 예외 체인을 익힙니다.
1. 예외란
실행 중 발생하는 오류. 처리하지 않으면 프로그램이 종료됨.
print(10 / 0) # ZeroDivisionError
print(int("abc")) # ValueError
print([1,2][5]) # IndexError2. try / except
try:
n = int(input("숫자: "))
print(10 / n)
except ValueError:
print("숫자가 아닙니다")
except ZeroDivisionError:
print("0으로 나눌 수 없습니다")
except Exception as e:
print(f"기타 에러: {e}")3. else / finally
- else: try가 예외 없이 끝났을 때
- finally: 예외 여부와 무관하게 항상 실행 (정리 작업)
try:
f = open("data.txt")
except FileNotFoundError:
print("파일 없음")
else:
print(f.read())
f.close()
finally:
print("정리 완료")4. raise
예외를 의도적으로 발생.
def withdraw(balance, amount):
if amount > balance:
raise ValueError("잔액 부족")
return balance - amount5. 사용자 정의 예외
Exception 상속.
class InsufficientFundsError(Exception):
pass
def transfer(account, amount):
if account.balance < amount:
raise InsufficientFundsError(f"잔액 {account.balance} < 요청 {amount}")6. 예외 체인
기존 예외를 감싸서 새 예외 발생.
try:
int("abc")
except ValueError as e:
raise RuntimeError("입력 처리 실패") from e7. 자주 하는 실수
- 너무 광범위한 except (except: pass) — 위험. 가능한 좁은 예외 클래스 명시
- except ... as e: pass — 에러를 숨김. 최소한 로그라도
- finally 에서 return — try의 return 값을 덮어쓸 수 있음
- except 순서 — 좁은 예외(FileNotFoundError) 먼저, 넓은 것(OSError) 뒤에
8. FAQ
Q1. 모든 함수에 try를 둘러싸야 하나요?
아닙니다. 회복할 수 있는 곳에서만. 보통 사용자 입력, 파일 I/O, 네트워크 호출.
Q2. Exception 과 BaseException 의 차이?
BaseException 이 최상위. KeyboardInterrupt, SystemExit 도 포함. 일반적으로는 Exception 까지만 잡음.
Q3. 예외 vs 반환값 (None / -1)?
예외는 "정상 흐름이 아님" 을 명확히. 검색에서 "없음" 같은 정상 상황은 None 권장.
6주차 — 파일 입출력 (텍스트·CSV·JSON)
with open 으로 안전하게 텍스트 파일 읽고 쓰기, csv 모듈로 표 형식 데이터 처리, json 으로 구조화 데이터 직렬화·역직렬화를 익힙니다.
1. 텍스트 파일 — with 문
파일을 자동으로 닫아줍니다.
# 쓰기
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("첫 줄\n")
f.writelines(["A\n", "B\n", "C\n"])
# 읽기
with open("notes.txt", encoding="utf-8") as f:
content = f.read() # 전체 한 번에
# 또는
for line in f: # 한 줄씩
print(line.rstrip())모드
| 모드 | 의미 |
|---|---|
| r | 읽기 (기본) |
| w | 쓰기 (기존 내용 삭제) |
| a | 추가 |
| r+ | 읽기/쓰기 |
| b | 바이너리 (예: rb, wb) |
항상 encoding="utf-8" 명시 (한글 안전).
2. CSV
쉼표로 구분된 표 형식.
import csv
# 쓰기
with open("scores.csv", "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["name", "score"])
w.writerows([["Alice", 92], ["Bob", 78]])
# 읽기
with open("scores.csv", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["name"], row["score"])newline="" 빠뜨리면 빈 줄 생김 (Windows).
3. JSON
구조화된 데이터를 텍스트로 직렬화.
import json
data = {
"name": "홍길동",
"skills": ["Python", "SQL"],
"active": True,
}
# 저장
with open("profile.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 로드
with open("profile.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["skills"])- ensure_ascii=False — 한글을 그대로
- indent=2 — 사람이 읽기 쉽게 들여쓰기
4. 자주 하는 실수
- open 후 close 누락 — with 사용으로 자동 처리
- encoding 누락 — Windows에서 한글 깨짐
- json.dump vs json.dumps — dump 는 파일에, dumps 는 문자열로
- CSV newline="" 누락 — Windows에서 빈 줄 추가됨
- JSON으로 저장 안 되는 타입 — datetime, 사용자 클래스. 변환 필요
5. FAQ
Q1. CSV 와 JSON 중 뭘 쓰나요?
표 형태 데이터는 CSV, 중첩·구조화 데이터는 JSON.
Q2. 큰 파일도 한 번에 읽어도 되나요?
메모리에 올라가므로 GB 단위는 위험. 라인 단위 순회 또는 제너레이터 사용.
Q3. pickle 은 뭔가요?
파이썬 객체를 그대로 저장. 단, 다른 언어에서 못 읽고 보안 위험. JSON 권장.
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
from datetime import datetime, date, timedelta
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M"))
today = date.today()
print("오늘:", today)
print("일주일 후:", today + timedelta(days=7))
birthday = date(2000, 5, 9)
print(f"태어난 후 {(today - birthday).days}일")
2026-05-10 14:32
오늘: 2026-05-10
일주일 후: 2026-05-17
태어난 후 9498일import random
random.seed(42) # 재현 가능
print(random.random())
print(random.randint(1, 100))
print(random.choice(["가위", "바위", "보"]))
print(random.sample(range(1, 46), 6))
xs = [1, 2, 3, 4, 5]
random.shuffle(xs)
print(xs)
0.6394267984578837
82
바위
[8, 14, 21, 27, 36, 41]
[3, 1, 4, 5, 2]from pathlib import Path
p = Path("data") / "hello.txt"
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("Hello!", encoding="utf-8")
print(p.read_text(encoding="utf-8"))
print(p.exists(), p.suffix, p.name, p.stem)
# 현재 폴더의 .py 파일 목록
for f in Path(".").glob("*.py"):
print(f.name)
Hello!
True .txt hello.txt hello
01_datetime.py
02_random.py
03_pathlib.py
04_collections.pyfrom collections import Counter, defaultdict, deque
text = "to be or not to be that is the question to be"
c = Counter(text.split())
print(c.most_common(3))
groups = defaultdict(list)
for name, age in [("A", 20), ("B", 25), ("C", 20)]:
groups[age].append(name)
print(dict(groups))
dq = deque([1, 2, 3])
dq.appendleft(0)
dq.append(4)
print(dq)
[('to', 3), ('be', 3), ('or', 1)]
{20: ['A', 'C'], 25: ['B']}
deque([0, 1, 2, 3, 4])try:
n = int(input("숫자: "))
print(10 / n)
except ValueError:
print("숫자가 아닙니다")
except ZeroDivisionError:
print("0으로 나눌 수 없습니다")
except Exception as e:
print(f"기타 에러: {e}")
숫자: abc
숫자가 아닙니다
숫자: 0
0으로 나눌 수 없습니다
숫자: 5
2.0try:
f = open("hello.txt", encoding="utf-8")
except FileNotFoundError:
print("파일 없음")
else:
print(f.read())
f.close()
finally:
print("정리 완료")
# 파일 있을 때
Hello, World!
정리 완료
# 파일 없을 때
파일 없음
정리 완료def withdraw(balance, amount):
if amount <= 0:
raise ValueError("금액은 양수여야 합니다")
if amount > balance:
raise ValueError("잔액 부족")
return balance - amount
try:
print(withdraw(1000, 500))
print(withdraw(500, 1000))
except ValueError as e:
print("에러:", e)
500
에러: 잔액 부족class InsufficientFundsError(Exception):
pass
class Account:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(
f"잔액 {self.balance} < 요청 {amount}"
)
self.balance -= amount
try:
a = Account(1000)
a.withdraw(2000)
except InsufficientFundsError as e:
print("거래 거부:", e)
거래 거부: 잔액 1000 < 요청 2000# 쓰기
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("첫 줄\n")
f.writelines(["A\n", "B\n", "C\n"])
# 읽기 (한 줄씩)
with open("notes.txt", encoding="utf-8") as f:
for line in f:
print(line.rstrip())
첫 줄
A
B
Cimport csv
with open("scores.csv", "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["name", "score"])
w.writerows([
["Alice", 92],
["Bob", 78],
["Charlie", 88],
])
print("저장 완료")
저장 완료
# scores.csv 내용:
name,score
Alice,92
Bob,78
Charlie,88import csv
with open("scores.csv", encoding="utf-8") as f:
for row in csv.DictReader(f):
name = row["name"]
score = int(row["score"])
print(f"{name}: {score}")
Alice: 92
Bob: 78
Charlie: 88import json
data = {
"name": "홍길동",
"skills": ["Python", "SQL"],
"active": True,
}
with open("profile.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open("profile.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["name"])
print(loaded["skills"])
홍길동
['Python', 'SQL']📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
D-Day 계산기
목표: 기념일까지의 D-Day를 계산해 출력.
- 기념일 입력 (YYYY-MM-DD)
- 오늘과의 차이 계산 후 D-N 또는 D+N 형태로 출력
기념일: 2026-12-25
D-229▶정답 코드 펼치기 / 접기
from datetime import date
target = date.fromisoformat(input("기념일: "))
today = date.today()
diff = (target - today).days
if diff > 0:
print(f"D-{diff}")
elif diff < 0:
print(f"D+{-diff}")
else:
print("D-DAY!")
기념일: 2026-12-25
D-229폴더 내 .txt 파일 크기 출력
목표: 현재 폴더의 모든 .txt 파일 이름과 크기를 한 줄씩.
- pathlib.Path(".").glob("*.txt") 사용
- f.stat().st_size 로 크기
notes.txt: 1234 bytes
todo.txt: 567 bytes▶정답 코드 펼치기 / 접기
from pathlib import Path
for f in Path(".").glob("*.txt"):
print(f"{f.name}: {f.stat().st_size} bytes")
notes.txt: 1234 bytes
todo.txt: 567 bytes단어 빈도 Top 5 (Counter)
목표: Counter로 가장 자주 등장한 단어 5개.
- text.split() + Counter + most_common(5)
apple: 5
banana: 4
cherry: 3
date: 2
egg: 2▶정답 코드 펼치기 / 접기
from collections import Counter
text = "apple banana apple cherry banana apple cherry banana apple cherry apple date egg banana date egg"
words = text.split()
for word, n in Counter(words).most_common(5):
print(f"{word}: {n}")
apple: 5
banana: 4
cherry: 3
date: 2
egg: 2안전한 정수 입력 (read_int)
목표: 잘못된 입력이면 다시 받는 read_int(prompt) 함수.
- ValueError 잡아서 다시 입력 받기
- while True 로 정상 입력될 때까지 반복
숫자: abc
숫자가 아닙니다. 다시 입력하세요.
숫자: 1.5
숫자가 아닙니다. 다시 입력하세요.
숫자: 42
입력값: 42▶정답 코드 펼치기 / 접기
def read_int(prompt):
while True:
try:
return int(input(prompt))
except ValueError:
print("숫자가 아닙니다. 다시 입력하세요.")
n = read_int("숫자: ")
print(f"입력값: {n}")
숫자: abc
숫자가 아닙니다. 다시 입력하세요.
숫자: 42
입력값: 42사용자 정의 예외 InvalidScoreError
목표: 0~100 범위가 아닌 점수면 InvalidScoreError.
- InvalidScoreError(Exception) 정의
- validate_score(s) 함수 — 잘못되면 raise
- 테스트로 try/except 처리
유효: 85
거부: 점수는 0~100 사이여야 합니다 (입력: 150)▶정답 코드 펼치기 / 접기
class InvalidScoreError(Exception):
pass
def validate_score(s):
if not (0 <= s <= 100):
raise InvalidScoreError(f"점수는 0~100 사이여야 합니다 (입력: {s})")
return s
for v in [85, 150]:
try:
validate_score(v)
print(f"유효: {v}")
except InvalidScoreError as e:
print(f"거부: {e}")
유효: 85
거부: 점수는 0~100 사이여야 합니다 (입력: 150)파일 읽기 예외 처리
목표: 파일이 없으면 친절한 메시지, 인코딩 오류면 다른 메시지.
- FileNotFoundError → "파일 없음"
- UnicodeDecodeError → "인코딩 오류"
- 정상 — 첫 5줄만 출력
파일 없음: data.txt▶정답 코드 펼치기 / 접기
def show_first_lines(path, n=5):
try:
with open(path, encoding="utf-8") as f:
for i, line in enumerate(f):
if i >= n:
break
print(line.rstrip())
except FileNotFoundError:
print(f"파일 없음: {path}")
except UnicodeDecodeError:
print(f"인코딩 오류: {path} (UTF-8 아님)")
show_first_lines("data.txt")
파일 없음: data.txt메모장 (텍스트 추가)
목표: 사용자 입력을 한 줄씩 notes.txt 에 추가.
- "a" 모드로 열기
- quit 입력 시 종료
- 각 줄에 시간 prefix
메모: 우유 사기
메모: 책 읽기
메모: quit
# notes.txt
[14:32] 우유 사기
[14:33] 책 읽기▶정답 코드 펼치기 / 접기
from datetime import datetime
while True:
text = input("메모: ")
if text == "quit":
break
with open("notes.txt", "a", encoding="utf-8") as f:
ts = datetime.now().strftime("%H:%M")
f.write(f"[{ts}] {text}\n")
메모: 우유 사기
메모: 책 읽기
메모: quit
# notes.txt
[14:32] 우유 사기
[14:33] 책 읽기CSV 점수 통계
목표: scores.csv 를 읽어 평균/최고/최저 점수와 인원 수.
- csv.DictReader 사용
- score 컬럼을 int로 변환
- 평균 소수점 1자리
인원: 3
평균: 86.0
최고: 92
최저: 78▶정답 코드 펼치기 / 접기
import csv
scores = []
with open("scores.csv", encoding="utf-8") as f:
for row in csv.DictReader(f):
scores.append(int(row["score"]))
print(f"인원: {len(scores)}")
print(f"평균: {sum(scores)/len(scores):.1f}")
print(f"최고: {max(scores)}")
print(f"최저: {min(scores)}")
인원: 3
평균: 86.0
최고: 92
최저: 78To-Do JSON 영구 저장
목표: 할 일을 추가/조회/저장하고 재시작 시에도 유지.
- todos.json 에 [{ id, text, done }] 형태로 저장
- 프로그램 시작 시 로드, 종료 시 저장
- 명령: add/list/done <id>/quit
> add 우유 사기
> list
1. [ ] 우유 사기
> done 1
> quit
저장 완료▶정답 코드 펼치기 / 접기
import json
from pathlib import Path
PATH = Path("todos.json")
def load():
if PATH.exists():
return json.loads(PATH.read_text(encoding="utf-8"))
return []
def save(todos):
PATH.write_text(json.dumps(todos, ensure_ascii=False, indent=2), encoding="utf-8")
todos = load()
next_id = max([t["id"] for t in todos], default=0) + 1
while True:
cmd = input("> ").strip()
if cmd == "quit":
save(todos)
print("저장 완료")
break
elif cmd == "list":
for t in todos:
mark = "x" if t["done"] else " "
print(f"{t['id']}. [{mark}] {t['text']}")
elif cmd.startswith("add "):
todos.append({"id": next_id, "text": cmd[4:], "done": False})
next_id += 1
elif cmd.startswith("done "):
i = int(cmd[5:])
for t in todos:
if t["id"] == i:
t["done"] = True
> add 우유 사기
> list
1. [ ] 우유 사기
> done 1
> quit
저장 완료댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.
댓글은 회원만 쓸 수 있습니다.
광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침