Ch6. Python 기초 — 파일 입출력과 예외처리
파일 입출력이란
프로그램이 종료되면 메모리에 저장된 데이터는 모두 사라집니다. **파일 입출력(File I/O)**을 통해 데이터를 파일로 저장하거나 파일에서 읽어올 수 있습니다.
파이썬의 파일 처리는 크게 세 단계로 이루어집니다:
- 파일 열기 —
open() - 읽기 또는 쓰기 — read/write 메서드
- 파일 닫기 —
close()
open() 함수
파일_객체 = open(파일경로, 모드, encoding=인코딩)
파일 열기 모드
| 모드 | 의미 | 파일이 없으면 |
|---|---|---|
'r' | 읽기(기본값) | 오류 발생 |
'w' | 쓰기 (파일 덮어쓰기) | 새로 생성 |
'a' | 추가 쓰기 (파일 끝에 추가) | 새로 생성 |
'x' | 독점 생성 (이미 있으면 오류) | 새로 생성 |
'rb' | 이진(binary) 읽기 | 오류 발생 |
'wb' | 이진(binary) 쓰기 | 새로 생성 |
# 텍스트 파일 쓰기
f = open("hello.txt", "w", encoding="utf-8")
f.write("안녕하세요!\n")
f.write("파이썬 파일 입출력입니다.\n")
f.close() # 반드시 닫아야 함!
with 문 — 자동으로 파일 닫기 (권장)
with 문을 사용하면 블록을 벗어날 때 자동으로 파일이 닫힙니다. close()를 깜빡하는 실수를 방지할 수 있어 강력히 권장됩니다.
with open("hello.txt", "w", encoding="utf-8") as f:
f.write("안녕하세요!\n")
f.write("파이썬 파일 입출력입니다.\n")
# with 블록을 벗어나면 자동으로 f.close() 호출
파일 읽기
read() — 전체 내용 읽기
with open("hello.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# 출력:
# 안녕하세요!
# 파이썬 파일 입출력입니다.
readline() — 한 줄씩 읽기
with open("hello.txt", "r", encoding="utf-8") as f:
line1 = f.readline() # 첫 번째 줄
line2 = f.readline() # 두 번째 줄
print(repr(line1)) # '안녕하세요!\n'
print(repr(line2)) # '파이썬 파일 입출력입니다.\n'
readlines() — 모든 줄을 리스트로
with open("hello.txt", "r", encoding="utf-8") as f:
lines = f.readlines() # ['안녕하세요!\n', '파이썬 파일 입출력입니다.\n']
for line in lines:
print(line.strip()) # strip()으로 줄바꿈 제거
for 루프로 읽기 (가장 효율적)
with open("hello.txt", "r", encoding="utf-8") as f:
for line in f:
print(line.strip())
파일 쓰기
write() — 문자열 쓰기
with open("output.txt", "w", encoding="utf-8") as f:
f.write("첫 번째 줄\n")
f.write("두 번째 줄\n")
f.write("세 번째 줄\n")
writelines() — 리스트의 각 요소 쓰기
lines = ["사과\n", "바나나\n", "딸기\n"]
with open("fruits.txt", "w", encoding="utf-8") as f:
f.writelines(lines)
파일에 내용 추가 (‘a’ 모드)
# 기존 파일 내용 유지하면서 추가
with open("log.txt", "a", encoding="utf-8") as f:
f.write("새로운 로그 항목\n")
CSV 파일 처리
CSV(Comma-Separated Values)는 데이터를 쉼표로 구분하는 텍스트 형식입니다.
import csv
# CSV 쓰기
header = ["이름", "나이", "도시"]
rows = [
["김철수", 25, "서울"],
["이영희", 30, "부산"],
["박민준", 22, "인천"],
]
with open("people.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(rows)
# CSV 읽기
with open("people.csv", "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['이름']} ({row['나이']}세) — {row['도시']}")
JSON 파일 처리
JSON은 웹 API와 설정 파일에서 널리 쓰이는 데이터 형식입니다.
import json
# 파이썬 딕셔너리 → JSON 파일
data = {
"name": "홍길동",
"age": 25,
"hobbies": ["독서", "코딩", "운동"],
"address": {"city": "서울", "zip": "04524"}
}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# JSON 파일 → 파이썬 딕셔너리
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["name"]) # 홍길동
print(loaded["hobbies"]) # ['독서', '코딩', '운동']
예외처리 (Exception Handling)
프로그램 실행 중 발생하는 오류를 **예외(Exception)**라고 합니다. 예외처리를 통해 오류가 발생해도 프로그램이 중단되지 않고 계속 실행되도록 할 수 있습니다.
주요 예외 종류
| 예외 이름 | 발생 상황 |
|---|---|
ValueError | 올바르지 않은 값 (예: int("abc")) |
TypeError | 올바르지 않은 타입 연산 (예: 1 + "a") |
ZeroDivisionError | 0으로 나눌 때 (예: 5 / 0) |
IndexError | 리스트 범위 벗어난 인덱스 |
KeyError | 딕셔너리에 없는 키 접근 |
FileNotFoundError | 파일을 찾을 수 없음 |
NameError | 정의되지 않은 변수 사용 |
AttributeError | 존재하지 않는 속성/메서드 접근 |
PermissionError | 파일 접근 권한 없음 |
RecursionError | 재귀 호출 깊이 초과 |
try-except 문
try:
# 예외가 발생할 수 있는 코드
위험한_코드
except 예외종류:
# 예외 발생 시 실행할 코드
처리_코드
# 기본 예외처리
try:
num = int(input("숫자를 입력하세요: "))
result = 10 / num
print(f"결과: {result}")
except ValueError:
print("오류: 숫자가 아닌 값을 입력했습니다.")
except ZeroDivisionError:
print("오류: 0으로는 나눌 수 없습니다.")
여러 예외 처리
try:
numbers = [1, 2, 3]
index = int(input("인덱스 입력: "))
print(numbers[index])
except ValueError:
print("정수를 입력해야 합니다.")
except IndexError:
print(f"인덱스 범위를 벗어났습니다. (0~{len(numbers)-1})")
except Exception as e:
# 예상치 못한 모든 예외 처리
print(f"알 수 없는 오류 발생: {e}")
else와 finally
try:
코드
except 예외:
예외_처리
else:
# try 블록이 예외 없이 정상 실행됐을 때
정상_처리
finally:
# 예외 발생 여부와 관계없이 항상 실행
정리_코드
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("0으로 나눌 수 없습니다.")
return None
else:
print("나눗셈 성공!")
return result
finally:
print("나눗셈 시도 완료.") # 항상 실행
print(safe_divide(10, 2))
print(safe_divide(10, 0))
출력:
나눗셈 성공!
나눗셈 시도 완료.
5.0
0으로 나눌 수 없습니다.
나눗셈 시도 완료.
None
예외 정보 활용
try:
result = int("abc")
except ValueError as e:
print(f"ValueError 발생: {e}")
# 출력: ValueError 발생: invalid literal for int() with base 10: 'abc'
예외 발생시키기 — raise
직접 예외를 발생시킬 수 있습니다.
def set_age(age):
if age < 0:
raise ValueError(f"나이는 0 이상이어야 합니다. 입력값: {age}")
if age > 150:
raise ValueError(f"유효하지 않은 나이입니다. 입력값: {age}")
return age
try:
set_age(-5)
except ValueError as e:
print(f"오류: {e}")
파일 처리 + 예외처리 결합
def read_file_safe(filename):
"""안전하게 파일을 읽는 함수"""
try:
with open(filename, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
print(f"오류: '{filename}' 파일을 찾을 수 없습니다.")
return None
except PermissionError:
print(f"오류: '{filename}' 파일 읽기 권한이 없습니다.")
return None
except UnicodeDecodeError:
print(f"오류: 인코딩 문제가 발생했습니다.")
return None
content = read_file_safe("없는파일.txt")
if content is None:
print("파일을 읽지 못했습니다.")
실전 예제 — 점수 파일 처리 프로그램
# score_processor.py
import json
def load_scores(filename):
"""점수 파일 읽기"""
try:
with open(filename, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print(f"'{filename}' 파일이 없습니다. 빈 데이터로 시작합니다.")
return {}
def save_scores(filename, data):
"""점수 파일 저장"""
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"'{filename}'에 저장 완료.")
def add_score(data, name, score):
"""점수 추가 (유효성 검사 포함)"""
if not isinstance(score, (int, float)):
raise TypeError("점수는 숫자여야 합니다.")
if not 0 <= score <= 100:
raise ValueError(f"점수는 0~100이어야 합니다. 입력: {score}")
data[name] = score
return data
# 실행
scores = load_scores("scores.json")
try:
scores = add_score(scores, "홍길동", 85)
scores = add_score(scores, "이순신", 92)
scores = add_score(scores, "유관순", 78)
save_scores("scores.json", scores)
except (TypeError, ValueError) as e:
print(f"입력 오류: {e}")
학습 정리
| 항목 | 핵심 내용 |
|---|---|
open() | 파일 열기. 모드: r/w/a/x/b |
with 문 | 자동 파일 닫기, 안전한 파일 처리 |
read() | 전체 읽기 |
readline() | 한 줄 읽기 |
readlines() | 전체를 줄 리스트로 |
write() | 문자열 쓰기 |
encoding | 한글 처리 시 utf-8 지정 |
try-except | 예외 발생 시 처리 |
finally | 예외 여부 무관 항상 실행 |
raise | 직접 예외 발생 |
실전 퀴즈 5문항
Q1. 파일을 열 때 'r'과 'a' 모드의 차이를 설명하고, 기존 파일에 새 줄을 추가하려면 어떤 모드를 사용해야 하나요?
A1.
'r'(read): 읽기 전용. 파일이 없으면FileNotFoundError발생.'a'(append): 추가 쓰기. 파일이 있으면 끝에 내용을 추가하고, 없으면 새로 생성. 기존 파일에 새 줄을 추가하려면'a'모드를 사용해야 합니다.'w'는 기존 내용을 모두 지우고 덮어씁니다.
Q2. with open(...) 문법을 사용하는 이유는 무엇인가요?
A2. with 문을 사용하면 블록을 벗어날 때 자동으로 close()가 호출됩니다. 예외가 발생해도 파일이 확실히 닫히므로, f.close() 호출을 잊어버리는 실수를 방지하고 시스템 자원 누수를 막을 수 있습니다.
Q3. 다음 코드에서 finally 블록이 출력되는 경우를 모두 나열하세요.
try:
x = int("abc")
print("성공")
except ValueError:
print("ValueError 발생")
finally:
print("finally 실행")
A3. finally 블록은 항상 실행됩니다. 이 코드에서는 int("abc")에서 ValueError가 발생하므로 "성공"은 출력되지 않고, "ValueError 발생" → "finally 실행" 순서로 출력됩니다. finally는 예외 발생 여부와 무관하게 실행됩니다.
Q4. except Exception as e:를 사용하는 이유와 주의사항은 무엇인가요?
A4. Exception은 거의 모든 예외의 부모 클래스이므로, 예상치 못한 모든 예외를 한 번에 잡을 수 있습니다. as e를 통해 오류 메시지를 확인할 수 있습니다. 다만 남용은 금물 — 어떤 예외가 발생했는지 모른 채 넘어가면 버그를 숨길 수 있습니다. 예상 가능한 예외는 구체적으로(ValueError, FileNotFoundError 등), 마지막 안전망으로만 Exception을 사용하는 것이 좋습니다.
Q5. 한글이 포함된 파일을 읽고 쓸 때 어떤 인코딩을 지정해야 하나요? 그리고 Windows에서 Excel이 읽는 CSV를 만들 때 특별히 사용하는 인코딩은 무엇인가요?
A5.
- 일반적으로 한글 파일 처리에는
encoding="utf-8"을 사용합니다. - Windows Excel이 바로 열 수 있는 CSV를 만들 때는
encoding="utf-8-sig"를 사용합니다.utf-8-sig는 BOM(Byte Order Mark)이 포함된 UTF-8로, Excel이 한글 인코딩을 올바르게 인식합니다.
OIYO 편집부
Content Editor지식 인큐베이터이자 전문 콘텐츠 크리에이터. 경영, 경제, 법률 및 실생활에 유용한 실무/자격증 중심의 깊이 있는 정보를 연구하고 공유합니다.