파일과 예외

실제로 작동하는 대부분의 프로그램은 파일 시스템에 접근합니다: 설정을 읽고, 결과를 쓰고, 데이터를 로드합니다. 그리고 무언가 잘못되면 Python은 예외를 발생시키는데, 이는 예상치 못한 일이 발생했음을 알리는 신호입니다. 이 장에서는 두 가지를 모두 다룹니다: 파일에서 데이터를 읽고 쓰기, 그리고 크래시 대신 오류를 우아하게 처리하는 코드 작성하기.
파일 열기
open()은 파일을 열고 읽거나 쓸 수 있는 객체를 반환합니다. 경로와 파일로 수행하려는 작업(읽기, 쓰기 또는 추가)을 지정합니다. 완료되면 항상 파일을 닫으세요. with 문은 이를 자동으로 수행합니다.
f = open("data.txt", "r") # "r" = 읽기
content = f.read()
f.close()"r"은 모드입니다:
| 모드 | 의미 |
|---|---|
"r" | 읽기. 파일이 반드시 존재해야 함. 기본 모드. |
"w" | 쓰기. 파일을 생성하거나 덮어씀. |
"a" | 추가. 지우지 않고 끝에 추가. |
"x" | 생성. 파일이 이미 존재하면 실패. |
"r+" | 읽기와 쓰기. |
"b" | 바이너리. 임의의 모드에 추가: "rb", "wb". |
작업이 끝나면 항상 .close()를 호출하세요. 잊으면 파일이 열린 채로 남아 아직 쓰기를 기다리는 데이터가 손실될 수 있습니다. 이를 처리하는 안정적인 방법은 with 문입니다.
open(path, mode)는 파일 객체를 주고, 모드는 수행할 작업을 나타냅니다: "r"은 읽기, "w"는 쓰기(거기 있던 것을 지움), "a"는 끝에 추가. 저는 한 번 "w"를 실수로 사용하여 파일을 잃었는데, "a"를 의도했었으므로 입력하기 전에 모드를 그려 보세요. with 문
with open(...)은 파일을 관리하며, 들여쓴 블록이 끝날 때 자동으로 닫습니다. 오류가 발생해도 닫힙니다. 항상 수동 open()/close() 대신 with open(...)을 사용하세요. 더 안전하고 표준입니다.
with open("data.txt", "r") as f:
content = f.read()
# f는 여기서 닫혀 있음, 보장됨with는 Python의 컨텍스트 매니저 문법입니다: 설정과 정리 코드를 실행하며, 여기서 파일을 열고 안정적으로 닫습니다. 내부적으로 어떻게 작동하는지 알 필요는 없습니다.
with open(...) as f는 파일을 열고, 들여쓴 블록 내에서 f로 작업하게 하고, 블록이 끝날 때 닫습니다. 뭔가 잘못되어도 닫힙니다. .close()를 직접 호출하는 대신 매번 이것을 사용하세요. 하나 덜 잊을 일입니다. 파일 읽기
세 가지 읽기 방법이 있습니다. .read()는 전체 파일을 하나의 문자열로 로드합니다. .readline()은 한 줄을 읽습니다. 파일 객체를 직접 반복하면 한 줄씩 읽는데, 이는 한 번에 모든 것을 메모리에 로드하지 않으므로 큰 파일에 가장 효율적입니다.
with open("data.txt", "r") as f:
content = f.read() # 전체 파일을 하나의 문자열로
with open("data.txt", "r") as f:
first_line = f.readline() # 한 번에 한 줄
with open("data.txt", "r") as f:
lines = f.readlines() # 리스트의 줄, 각 줄이 "\n"으로 끝남큰 파일의 경우 한 번에 모든 것을 로드하는 것보다 한 줄씩 읽는 것이 더 효율적입니다:
with open("big_file.txt", "r") as f:
for line in f: # 파일을 직접 반복, 메모리 효율적
print(line.strip()) # strip()은 뒤의 개행을 제거파일 객체를 직접 반복(for line in f)하는 것이 큰 파일을 읽는 가장 효율적인 방법입니다.
.read()는 전체 파일을 하나의 문자열로 제공하고, .readline()은 단일 줄을 제공합니다. 파일이 클 수 있을 때 대신 `for line in f`로 루프하세요. 한 번에 한 줄씩 읽으므로 메모리에 모든 것을 로드하지 않습니다. 각 줄의 `.strip()`은 뒤의 개행을 지웁니다. 파일 쓰기
"w" 모드는 존재할 경우 파일 전체를 덮어씁니다. "a" 모드는 끝에 추가합니다. .write()는 자동으로 개행을 추가하지 않습니다. 각 줄 끝에 "\n"을 명시적으로 포함하세요. 여러 줄을 한 번에 쓰려면 "\n".join()으로 결합하세요.
with open("output.txt", "w") as f:
f.write("Hello, world\n")
with open("output.txt", "a") as f:
f.write("Another line\n")"w"는 존재하면 파일 전체를 덮어씁니다. "a"는 끝에 추가합니다.
f.write()는 자동으로 개행을 추가하지 않으므로 "\n"을 명시적으로 포함하세요. 여러 줄을 한 번에 쓰려면:
lines = ["Line one", "Line two", "Line three"]
with open("output.txt", "w") as f:
f.write("\n".join(lines) + "\n")"w"는 파일을 생성하거나 덮어씁니다. "a"는 끝에 추가합니다. .write()는 개행을 추가하지 않으므로 각 줄에 `"\n"`을 직접 붙이세요. 일괄 줄의 경우 `"\n".join(lines)`이 먼저 이들을 합칩니다. 예외
Python이 처리할 수 없는 문제를 만나면 예외를 발생시킵니다: 무엇이 잘못되었고 어디에서 발생했는지 설명하는 오류. 이를 처리하지 않으면 프로그램이 크래시하고 추적을 인쇄합니다. 아래 표는 마주칠 가장 일반적인 예외를 보여줍니다.
마주칠 일반적인 예외:
| 예외 | 발생하는 시기 |
|---|---|
FileNotFoundError | open()이 파일을 찾을 수 없음 |
ValueError | 함수가 올바른 유형이지만 잘못된 내용의 값을 받음, 예: int("abc") |
TypeError | 완전히 잘못된 유형, 예: "hello" + 5 |
KeyError | 딕셔너리 키가 존재하지 않음 |
IndexError | 리스트 인덱스가 범위를 벗어남 |
ZeroDivisionError | 0으로 나누기 |
AttributeError | 객체가 그 속성이나 메서드를 가지고 있지 않음 |
try / except
실패할 수 있는 코드를 try 블록으로 감싸세요. 예외가 발생하면 일치하는 except 블록이 크래시 대신 처리합니다. 어떤 예외를 잡을지 구체적으로 지정하세요: except:로 모든 것을 잡으면 실제 버그를 숨깁니다.
try:
value = int("abc")
except ValueError:
print("That's not a valid number")어떤 예외를 잡을지 구체적으로 지정하세요. 모든 것을 except:로 잡으면 버그를 숨깁니다:
# 나쁨, 프로그래머 실수를 포함한 모든 것을 잡음
try:
result = do_something()
except:
pass
# 좋음, 예상하고 실제로 처리할 수 있는 것만 잡음
try:
result = do_something()
except FileNotFoundError:
print("File not found")여러 예외 잡기
서로 다른 오류 유형을 별도의 except 블록에서 처리하거나 튜플을 사용하여 한 블록에서 여러 유형을 잡을 수 있습니다. as e 부분은 오류 메시지에 접근할 수 있게 합니다.
try:
data = int(user_input)
result = 100 / data
except ValueError:
print("Not a number")
except ZeroDivisionError:
print("Can't divide by zero")또는 튜플에서 여러 개를 잡으세요:
except (ValueError, ZeroDivisionError) as e:
print(f"Input error: {e}")as e는 예외 객체를 이름에 바인딩하므로 메시지를 검사할 수 있습니다.
else와 finally
else는 예외가 발생하지 않은 경우에만 실행됩니다. finally는 예외 발생 여부와 관계없이 항상 실행됩니다. finally는 무엇이 일어나든 상관없이 발생해야 하는 정리에 유용합니다.
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("File not found, using defaults")
content = ""
else:
print("File loaded successfully")
finally:
print("Done attempting to load file") # 항상 실행됨finally는 파일용 with을 이미 사용하고 있어도 정리(연결 닫기, 락 해제)에 가장 유용합니다.
raise
raise로 예외를 직접 발생시킬 수 있습니다. 이것은 함수가 조용히 잘못된 값을 반환하는 대신 명확하게 문제를 발신자에게 알리는 방법입니다.
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b이것은 함수가 기대하는 것을 명시하고 문제를 발신자에게 명확하게 알립니다.
사용자 정의 예외 클래스
더 큰 프로그램의 경우 Exception을 상속하여 자신의 예외 유형을 정의할 수 있습니다. 이것은 발신자가 다른 종류의 오류와 별도로 자신의 특정 오류를 잡을 수 있게 합니다.
class InsufficientFundsError(Exception):
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(
f"Cannot withdraw {amount}, balance is {self.balance}"
)
self.balance -= amounttry:
account.withdraw(1000)
except InsufficientFundsError as e:
print(f"Transaction declined: {e}")JSON
JSON은 모든 것이 말하는 형식입니다: API, 설정 파일, 데이터 내보내기. Python의 json 모듈은 이를 직접 처리합니다. json.load()는 파일에서 JSON을 읽어 Python 딕셔너리 또는 리스트로 변환합니다. json.dump()는 딕셔너리 또는 리스트를 다시 파일로 JSON으로 씁니다.
파일에서 JSON 읽기:
import json
with open("config.json", "r") as f:
config = json.load(f) # JSON을 Python dict/list로 파싱
print(config["setting"])파일에 JSON 쓰기:
import json
data = {"name": "민준", "score": 87, "active": True}
with open("output.json", "w") as f:
json.dump(data, f, indent=2) # indent= 는 사람이 읽을 수 있게 함JSON에서 Python 유형으로의 매핑:
| JSON | Python |
|---|---|
object {} | dict |
array [] | list |
string "" | str |
| number | int 또는 float |
true / false | True / False |
null | None |
파일에 접하지 않고 JSON 문자열과 Python 객체 사이를 변환하려면:
import json
# 문자열에서 Python으로
data = json.loads('{"name": "민준", "score": 87}')
# Python에서 문자열로
text = json.dumps({"name": "민준", "score": 87}, indent=2)json.load()는 파일 객체에서 읽습니다. json.loads()(들은 "s" 포함)는 문자열에서 읽습니다.
실제 사용
작은 게임을 위한 저장/로드 패턴: 상태를 JSON으로 씁니다. 다음 실행에서 다시 로드하고, 저장 파일이 아직 없으면 기본값으로 돌아갑니다:
import json
SAVE_FILE = "save_game.json"
def save_game(player_data: dict) -> None:
with open(SAVE_FILE, "w") as f:
json.dump(player_data, f, indent=2)
print("Game saved.")
def load_game() -> dict:
try:
with open(SAVE_FILE, "r") as f:
return json.load(f)
except FileNotFoundError:
print("No save file found, starting fresh.")
return {"name": "Player", "score": 0, "level": 1}
state = load_game()
state["score"] += 50
save_game(state)
