학습 순서 8번. 새로 작성한 챕터입니다. 데이터 분석으로 가는 마지막 관문입니다.
지금까지 데이터는 전부 코드 안에 직접 적어 둔 것이었습니다. 실제로는 파일에서 읽어 옵니다.
이 챕터에서 CSV를 읽으면 step05 에서 배운 "리스트 안의 딕셔너리" 가 그대로 나옵니다. pandas의 read_csv() 가 하는 일이 정확히 이것입니다 — 여기서 손으로 해 보면 나중에 pandas가 마법이 아니라 편의 도구로 보입니다.
open()/with로 파일을 안전하게 읽고 쓴다- 파일 모드(
r·w·a)의 차이를 안다. 특히w가 기존 내용을 지운다는 것 try/except/else/finally로 에러를 다룬다- 예외를 종류별로 잡는다 (뭉뚱그리지 않는다)
raise로 직접 예외를 일으키고 내 예외를 정의한다- 표준
csv모듈로 실제 데이터 파일을 읽고 집계해 저장한다
| 파일 | 내용 |
|---|---|
data/scores.csv |
실습 데이터 — 12명의 학년·반·3과목 점수 |
writeEx.py |
파일 쓰기, with, 모드, print(file=...) |
readEx.py |
read/readlines/순회/readline, FileNotFoundError |
exceptionEx1.py |
★ try/except/else/finally |
exceptionEx2.py |
★ raise, 사용자 예외, 나쁜 예외 처리 3종 |
csvEx1.py |
★★ CSV 읽기 — DictReader, 숫자 변환, 집계 |
csvEx2.py |
CSV 쓰기 — writer, DictWriter |
test1.py |
연습문제 — 읽기 → 집계 → 저장 한 바퀴 |
output/ |
예제가 만들어 내는 파일 위치 (.gitignore 처리) |
cd step08_file # ← 상대 경로를 쓰므로 이 폴더에서 실행하세요
python3 writeEx.py # 먼저 실행해서 파일을 만듭니다
python3 readEx.py
python3 csvEx1.py
python3 test1.py전부 입력 없이 실행됩니다. 결과 파일은 output/ 에 생기고 커밋되지 않습니다.
# 이렇게 하지 말고
f = open('memo.txt', 'w', encoding='utf-8')
f.write('내용')
f.close() # ← 빠뜨리기 쉽고, 중간에 에러 나면 실행 안 됨
# 이렇게
with open('memo.txt', 'w', encoding='utf-8') as f:
f.write('내용') # 블록을 벗어나면 자동으로 닫힌다with 는 에러가 나도 파일을 닫아 줍니다. 닫지 않은 파일은 내용이 디스크에 안 써질 수 있고, 파일 핸들이 쌓이면 프로그램이 죽습니다.
| 모드 | 동작 |
|---|---|
'r' |
읽기 (기본값). 파일이 없으면 FileNotFoundError |
'w' |
쓰기. 파일이 있으면 내용을 통째로 지우고 새로 씁니다 |
'a' |
추가. 기존 내용 뒤에 이어 씁니다 |
'x' |
배타 생성. 파일이 이미 있으면 에러 |
'w' 로 실수로 열어 데이터를 날리는 사고가 정말 자주 납니다. 이어 쓸 생각이면 'a' 인지 확인하세요.
지정하지 않으면 운영체제 기본값을 씁니다. 윈도우는 cp949, macOS·리눅스는 utf-8 이라 한글이 든 파일을 주고받으면 깨집니다. 습관으로 만드세요.
f.read() # 통째로 문자열 하나 — 작은 파일에만
f.readlines() # 줄 단위 리스트 — 전부 메모리에 올린다
f.readline() # 한 줄씩 직접
for line in f: # ★ 권장 — 한 줄씩 읽으며 처리. 큰 파일도 견딘다로그 파일이 1GB라면 read() 는 메모리를 1GB 먹지만 for line in f 는 한 줄씩만 씁니다.
읽은 줄에는 줄바꿈 문자가 붙어 있습니다. line.strip() 으로 떼세요.
# 나쁜 예 — 무엇이 잘못됐는지 알 수 없다
try:
return 100 / int(text)
except Exception:
print('에러가 났습니다')
# 더 나쁜 예 — 아무 일도 없었다는 듯이 넘어간다
except Exception:
pass
# 좋은 예
except ValueError:
print(f'"{text}" 는 숫자가 아닙니다')
except ZeroDivisionError:
print('0으로 나눌 수 없습니다')test/class_ex_01.py 가 첫 번째 함정에 빠져 있었습니다. ZeroDivisionError 를 잡아 놓고 "숫자를 입력하세요"라고 말했죠. step07_class/calculator.py 에서 고친 그 부분입니다.
except Exception: pass 는 최악입니다. 버그가 조용히 사라졌다가 엉뚱한 곳에서 None 으로 터집니다.
try:
n = int(text)
except ValueError:
print('실패')
else:
print('성공 :', n) # 예외가 없었을 때만
finally:
print('항상 실행') # 있든 없든else 는 "예외가 안 났을 때"입니다. try 안에 성공 처리까지 넣지 않고 else 로 빼면, 어느 줄에서 난 예외인지 명확해집니다.
with open('data/scores.csv', 'r', encoding='utf-8') as f:
students = list(csv.DictReader(f))
print(students[0])
# {'이름': '홍길동', '학년': '1', '반': '1', '국어': '85', '영어': '73', '수학': '68'}DictReader 는 첫 줄(헤더)을 키로 써서 각 줄을 딕셔너리로 만들어 줍니다. 결과가 정확히 step05_datastructure/dictEx2.py 에서 다룬 "리스트 안의 딕셔너리"입니다.
print(type(students[0]['국어'])) # <class 'str'>
print(students[0]['국어'] + students[0]['영어']) # '8573' — 문자열 연결!
print(int(...) + int(...)) # 158 — 이게 맞다step02_operator/parseInt.py 의 "키보드 파일 웹에서 읽어오는 데이터는 모두 문자열" 이 그대로 적용됩니다. 계산하기 전에 int()·float() 로 변환하세요.
이걸 놓치면 "합계가 이상하게 크다"는 증상으로 나타납니다. 숫자를 더한 게 아니라 문자열을 이어 붙였기 때문입니다.
pandas의
read_csv()는 이 변환을 자동으로 해 줍니다. 여기서 손으로 해 봤기 때문에 나중에 그 편리함이 무엇을 대신해 주는지 알 수 있습니다.
by_grade = {}
for r in rows:
by_grade.setdefault(r['학년'], []).append(r['평균'])"키가 없으면 빈 리스트를 넣고, 그 리스트를 돌려준다". step05 의 개수 세기(counter.get(c, 0) + 1)와 같은 계열의 관용구입니다.
이게 pandas의 groupby() 가 하는 일입니다.
with open('out.csv', 'w', encoding='utf-8', newline='') as f:이걸 빠뜨리면 윈도우에서 줄 사이에 빈 줄이 하나씩 끼어듭니다. csv 모듈이 줄바꿈을 직접 관리하는데, 파일 객체가 한 번 더 변환해서 생기는 문제입니다.
| 증상 | 원인 |
|---|---|
FileNotFoundError |
상대 경로 기준이 다름. cd step08_file 후 실행하세요 |
| 파일 내용이 사라짐 | 'a' 대신 'w' 로 열었음 |
| 한글이 깨짐 | encoding='utf-8' 누락 |
| 합계가 이상하게 큼 | CSV 값을 int() 로 변환 안 함 (문자열 연결됨) |
| CSV에 빈 줄이 끼어듦 | newline='' 누락 |
| 마지막에 줄바꿈이 붙음 | line.strip() 안 함 |
SyntaxError: encoding problem |
아래 참고 |
writeEx.py 의 2번째 줄에 이렇게 썼다가 파일이 실행되지 않았습니다.
# open(경로, 모드, encoding=...) 으로 열고, 다 쓰면 close() 로 닫는다SyntaxError: encoding problem: ...
파이썬은 파일의 첫 두 줄에서 # ...coding=이름 패턴을 찾으면 그것을 소스 파일의 인코딩 선언(PEP 263)으로 해석합니다. encoding=... 이 걸렸고, ... 은 유효한 인코딩 이름이 아니라 파싱이 실패한 것입니다.
주석인데도 문법에 영향을 주는, 파이썬에서 몇 안 되는 경우입니다. writeEx.py 위쪽에 이 내용을 적어 두었습니다.
writeEx.py를 두 번 실행하고memo2.txt를 열어 보세요.'w'로 쓴 부분과'a'로 쓴 부분이 각각 어떻게 되나요?readEx.py의for line in f순회를readlines()방식으로 바꿔 보고, 1GB 파일이라면 왜 문제가 되는지 설명해 보세요.exceptionEx2.py의worse()함수가 왜 최악인지 — 호출한 쪽에서 어떤 일이 벌어지는지 설명하세요.csvEx1.py에 반별 최고점 학생을 구하는 코드를 추가하세요.test1.py에 석차(평균 내림차순)를 추가해result.csv에 함께 저장하세요.data/scores.csv에 일부러 잘못된 줄(점수 자리에abc)을 넣고, 그 줄만 건너뛰고 나머지는 처리하도록try/except를 넣으세요.step07_class/calculator.py의 계산 기록을 CSV로 남기도록 고치세요.