Skip to content

Latest commit

 

History

History

step08_file

Folders and files

README.md

step08_file — 파일 입출력 · 예외 처리 · CSV

학습 순서 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/ 에 생기고 커밋되지 않습니다.

짚고 갈 지점

with 를 쓰세요

# 이렇게 하지 말고
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 는 에러가 나도 파일을 닫아 줍니다. 닫지 않은 파일은 내용이 디스크에 안 써질 수 있고, 파일 핸들이 쌓이면 프로그램이 죽습니다.

w 모드는 기존 내용을 지웁니다

모드 동작
'r' 읽기 (기본값). 파일이 없으면 FileNotFoundError
'w' 쓰기. 파일이 있으면 내용을 통째로 지우고 새로 씁니다
'a' 추가. 기존 내용 뒤에 이어 씁니다
'x' 배타 생성. 파일이 이미 있으면 에러

'w' 로 실수로 열어 데이터를 날리는 사고가 정말 자주 납니다. 이어 쓸 생각이면 'a' 인지 확인하세요.

encoding='utf-8' 을 항상 쓰세요

지정하지 않으면 운영체제 기본값을 씁니다. 윈도우는 cp949, macOS·리눅스는 utf-8 이라 한글이 든 파일을 주고받으면 깨집니다. 습관으로 만드세요.

파일 읽기 4가지, 무엇을 쓸까

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 으로 터집니다.

else 와 finally

try:
    n = int(text)
except ValueError:
    print('실패')
else:
    print('성공 :', n)      # 예외가 없었을 때만
finally:
    print('항상 실행')       # 있든 없든

else 는 "예외가 안 났을 때"입니다. try 안에 성공 처리까지 넣지 않고 else 로 빼면, 어느 줄에서 난 예외인지 명확해집니다.

CSV를 읽으면 step05 의 그 구조가 나옵니다

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 에서 다룬 "리스트 안의 딕셔너리"입니다.

⚠️ CSV에서 읽은 값은 전부 문자열입니다

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() 는 이 변환을 자동으로 해 줍니다. 여기서 손으로 해 봤기 때문에 나중에 그 편리함이 무엇을 대신해 주는지 알 수 있습니다.

setdefault — 그룹으로 묶기

by_grade = {}
for r in rows:
    by_grade.setdefault(r['학년'], []).append(r['평균'])

"키가 없으면 빈 리스트를 넣고, 그 리스트를 돌려준다". step05 의 개수 세기(counter.get(c, 0) + 1)와 같은 계열의 관용구입니다.

이게 pandas의 groupby() 가 하는 일입니다.

CSV 쓸 때는 newline=''

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 위쪽에 이 내용을 적어 두었습니다.

연습 과제

  1. writeEx.py 를 두 번 실행하고 memo2.txt 를 열어 보세요. 'w' 로 쓴 부분과 'a' 로 쓴 부분이 각각 어떻게 되나요?
  2. readEx.py 의 for line in f 순회를 readlines() 방식으로 바꿔 보고, 1GB 파일이라면 왜 문제가 되는지 설명해 보세요.
  3. exceptionEx2.py 의 worse() 함수가 왜 최악인지 — 호출한 쪽에서 어떤 일이 벌어지는지 설명하세요.
  4. csvEx1.py 에 반별 최고점 학생을 구하는 코드를 추가하세요.
  5. test1.py 에 석차(평균 내림차순)를 추가해 result.csv 에 함께 저장하세요.
  6. data/scores.csv 에 일부러 잘못된 줄(점수 자리에 abc)을 넣고, 그 줄만 건너뛰고 나머지는 처리하도록 try/except 를 넣으세요.
  7. step07_class/calculator.py 의 계산 기록을 CSV로 남기도록 고치세요.