27. 파일 읽기와 쓰기
지금까지 우리 프로그램의 모든 데이터는 임시적이었습니다—프로그램이 끝나면 사라지는 변수에 저장되어 있었죠. 실행 사이에도 정보를 기억하는 프로그램을 만들려면 파일을 다뤄야 합니다. 파일은 데이터를 디스크에 저장하고 나중에 다시 읽어 올 수 있게 해 주며, 설정 값부터 사용자 데이터 저장까지 다양한 일을 가능하게 합니다.
이 장에서는 Python에서 텍스트 파일을 읽고 쓰는 방법을 배웁니다. 먼저 파일 경로(file path)와 Python이 파일을 찾는 방법을 이해한 뒤, 파일을 열고(open), 읽고(read), 쓰고(write), 올바르게 닫는(close) 과정으로 넘어갑니다. 또한 다양한 파일 모드(file mode), 텍스트 인코딩(text encoding), 그리고 파일 작업 중 흔히 발생하는 오류를 처리하는 방법도 배우게 됩니다.
27.1) 파일 경로와 현재 작업 디렉터리
파일을 다루기 전에, Python이 컴퓨터의 파일 시스템에서 파일을 어떻게 찾는지 이해해야 합니다.
27.1.1) 파일 경로 이해하기
파일 경로(file path) 는 컴퓨터에서 파일의 주소입니다. Python에게 파일을 어디서 찾아야 하는지 정확히 알려줍니다. 경로에는 두 가지 종류가 있습니다.
절대 경로(absolute path) 는 파일 시스템의 루트부터 전체 위치를 지정합니다:
- Windows:
C:\Users\Alice\Documents\data.txt - macOS/Linux:
/home/alice/documents/data.txt
상대 경로(relative path) 는 현재 작업 디렉터리를 기준으로 한 위치를 지정합니다:
data.txt(현재 디렉터리에 있는 파일)reports/sales.txt(하위 디렉터리에 있는 파일)../config.txt(상위 디렉터리에 있는 파일)
현재 작업 디렉터리(current working directory, CWD) 는 상대 경로를 사용할 때 Python이 파일을 찾는 폴더입니다. Python 스크립트를 실행할 때 CWD는 명령을 실행한 디렉터리이며, 반드시 스크립트 파일이 위치한 곳은 아닙니다.
예를 들어:
# 디렉터리 구조:
/home/alice/
└── projects/
└── script.py
# projects 폴더에서 실행:
$ cd /home/alice/projects
$ python script.py
# CWD는 /home/alice/projects
# 상위 폴더에서 실행:
$ cd /home/alice
$ python projects/script.py
# CWD는 /home/alice (script.py가 있는 곳이 아닙니다!)현재 작업 디렉터리는 다음과 같이 확인할 수 있습니다.
import os
# 현재 작업 디렉터리 가져오기
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")Output:
Current directory: /home/alice/projects/file_demoos.getcwd() 함수는 현재 작업 디렉터리의 절대 경로를 반환합니다. 이는 상대 경로를 사용할 때 Python이 파일을 어디에서 찾을지 이해하는 데 유용합니다.
현재 작업 디렉터리를 이해하는 것은 중요합니다. "data.txt" 같은 상대 경로를 사용할 때 Python이 어디를 찾을지 결정하기 때문입니다. /home/alice/projects/에서 스크립트를 실행하고 "data.txt"를 열면, Python은 /home/alice/projects/data.txt를 찾습니다.
27.1.2) 상대 경로를 효과적으로 사용하기
파일을 다룰 때, 상대 경로는 절대 경로보다 더 편리한 경우가 많습니다. 상대 경로를 사용하면 코드가 이식 가능해집니다. 즉, 프로젝트 폴더가 다른 컴퓨터의 어디에 위치하든 상관없이 작동합니다.
다음은 일반적인 상대 경로 패턴입니다:
# 현재 작업 디렉터리에 있는 파일
filename = "student_grades.txt"
# 하위 디렉터리에 있는 파일 (현재 디렉터리 안의 data 폴더)
filename = "data/student_grades.txt"
# 상위 디렉터리에 있는 파일
filename = "../shared_data.txt"이 챕터의 예제에서는 주로 "data.txt"와 같은 간단한 파일명을 사용합니다. 이것의 의미는:
- 데이터 파일이 Python 명령을 실행하는 디렉터리와 같은 곳에 있어야 합니다
- 스크립트가
/home/alice/projects/에 있고 그 디렉터리에서python script.py를 실행하면, Python은/home/alice/projects/에서data.txt를 찾습니다
이러한 접근 방식은 경로 탐색보다는 파일 작업에 집중할 수 있도록 예제를 명확하고 간결하게 유지합니다. FileNotFoundError가 발생하면 os.getcwd()를 사용하여 Python이 어디에서 파일을 찾고 있는지 확인하세요.
27.1.3) 운영체제별 경로 구분자
운영체제마다 경로에서 디렉터리를 구분하는 문자가 다릅니다:
- Windows는 백슬래시를 사용합니다:
data\reports\sales.txt - macOS와 Linux는 슬래시를 사용합니다:
data/reports/sales.txt
Python에서는 코드에서 슬래시를 사용해도 자동으로 처리되며, 모든 운영체제에서 동작합니다:
# Windows, macOS, Linux에서 모두 동작합니다
filename = "data/reports/sales.txt"Python은 슬래시를 운영체제에 맞는 구분자로 변환합니다.
27.2) 파일 열기와 닫기
파일을 다루려면 먼저 열어(open) 야 하며, 이는 프로그램과 디스크의 파일 사이에 연결을 만듭니다. 작업이 끝나면 닫아(close) 서 시스템 리소스를 해제하고 모든 데이터가 제대로 저장되도록 해야 합니다.
27.2.1) open() 함수
open() 함수는 파일과의 연결을 나타내는 파일 객체(file object)를 생성합니다. 가장 단순한 형태에서는 파일 이름을 전달하면 됩니다:
# 읽기용으로 파일 열기 (디폴트 모드)
file = open("message.txt")이 코드는 현재 디렉터리에 있는 message.txt 파일을 엽니다. open() 함수는 파일 객체를 반환하고, 우리는 이를 file 변수에 저장합니다. 이 객체는 파일에서 읽거나 쓰기 위한 메서드를 제공합니다.
하지만 이 기본 접근에는 치명적인 문제가 있습니다. 파일을 연 뒤 오류가 발생하면 파일이 영원히 닫히지 않을 수도 있습니다. 파일을 닫는 것이 왜 중요한지 살펴봅시다.
27.2.2) 파일을 닫는 것이 중요한 이유
파일을 열면 운영체제는 그 연결을 유지하기 위해 리소스를 할당합니다. 파일을 닫지 않으면:
- 데이터가 저장되지 않을 수 있습니다: 파일에 쓸 때 데이터는 종종 메모리에 버퍼링되며, 파일이 닫힐 때 디스크에 기록됩니다
- 시스템 리소스가 낭비됩니다: 열려 있는 각 파일은 메모리와 파일 핸들(file handle)을 소비합니다
- 다른 프로그램이 차단될 수 있습니다: 일부 시스템에서는 이미 열려 있는 파일에 다른 프로그램이 접근하지 못하게 합니다
파일을 닫으려면 close() 메서드를 호출합니다:
file = open("message.txt")
# ... 파일로 작업 ...
file.close() # 리소스를 해제하고 데이터가 저장되도록 보장27.2.3) 수동으로 닫을 때의 문제
파일을 수동으로 닫는 것은 오류가 발생하기 쉽습니다. 열기와 닫기 사이에 예외(exception)가 발생하면 close() 호출이 실행되지 않을 수 있습니다:
file = open("data.txt")
result = process_data(file) # 여기서 예외가 발생하면...
file.close() # ...이 코드는 실행되지 않습니다!이 문제는 너무 흔해서 Python은 더 나은 해결책을 제공합니다. 27.4절에서 배울 with 문입니다. 지금은 파일을 수동으로 열고 닫는 작업은 close()가 항상 호출되도록 세심한 주의가 필요하다는 점만 이해하세요.
27.2.4) 파일이 열려 있는지 확인하기
파일 객체에는 파일이 닫혔는지 알려주는 closed 속성이 있습니다:
file = open("data.txt")
print(file.closed) # Output: False
file.close()
print(file.closed) # Output: True파일이 닫힌 뒤에 읽거나 쓰려고 하면 오류가 발생합니다:
file = open("data.txt")
file.close()
# This raises ValueError: I/O operation on closed file
content = file.read()오류 메시지는 문제를 명확히 알려 줍니다. 이미 닫힌 파일에 대해 I/O(input/output) 작업을 수행하려 하고 있다는 뜻입니다.
27.3) 파일 모드(r, w, a, 텍스트 vs 바이너리)와 인코딩 이해하기
파일을 열 때 모드(mode) 를 지정할 수 있는데, 이는 어떤 작업이 허용되는지와 파일이 어떻게 처리되는지를 결정합니다. 모드를 이해하는 것은 파일을 올바르게 다루는 데 필수입니다.
27.3.1) 텍스트 모드 vs 바이너리 모드
파일은 두 가지 기본 모드로 열 수 있습니다.
텍스트 모드(text mode) (기본값)는 파일이 텍스트를 담고 있다고 취급합니다. Python은 자동으로:
- 플랫폼과 관계없이 줄바꿈을
\n으로 변환합니다 - 텍스트 인코딩을 처리합니다(바이트와 문자열 간 변환)
- 문자열을 읽고 쓸 수 있게 합니다
바이너리 모드(binary mode) 는 파일을 원시 바이트(raw bytes)로 취급합니다. Python은:
- 문자열이 아니라 bytes 객체를 읽고 씁니다
- 어떤 변환이나 해석도 하지 않습니다
- 이미지, 오디오, 비디오 등 텍스트가 아닌 파일에 사용합니다
이 장에서는 가장 자주 사용하게 될 텍스트 모드에 집중하겠습니다. 바이너리 모드는 모드 문자열에 'b'를 추가해(예: 'rb', 'wb') 표시하지만, 텍스트 파일에는 필요하지 않습니다.
27.3.2) 세 가지 주요 파일 모드
Python은 텍스트 파일을 열기 위한 세 가지 주요 모드를 제공합니다.
읽기 모드('r') - 읽기 전용으로 파일을 엽니다:
file = open("data.txt", "r") # 또는 그냥 open("data.txt")- 파일이 이미 존재해야 하며, 그렇지 않으면 Python이
FileNotFoundError를 발생시킵니다 - 파일에서 읽을 수 있지만 쓸 수는 없습니다
- 모드를 지정하지 않으면 기본값입니다
쓰기 모드('w') - 쓰기용으로 파일을 엽니다:
file = open("output.txt", "w")- 파일이 없으면 생성합니다
- 파일이 이미 존재하면 기존 내용을 모두 지웁니다
- 파일에 쓸 수 있지만 읽을 수는 없습니다
- 새 파일을 만들거나 기존 파일을 완전히 교체하려는 경우 사용합니다
추가 모드('a') - 파일 끝에 내용을 추가하기 위해 엽니다:
file = open("log.txt", "a")- 파일이 없으면 생성합니다
- 기존 내용을 유지하고 끝에 새 내용을 추가합니다
- 파일에 쓸 수 있지만 읽을 수는 없습니다
- 기존 내용을 잃지 않고 파일에 내용을 추가하려는 경우 사용합니다
다음은 이러한 모드가 기존 파일에 어떤 영향을 미치는지 비교한 예입니다:
# data.txt의 내용이 "Hello\nWorld\n"라고 가정합니다
# 읽기 모드 - 내용 변경 없음
file = open("data.txt", "r")
file.close()
# 파일에는 여전히 "Hello\nWorld\n"가 들어 있습니다
# 쓰기 모드 - 내용이 지워짐
file = open("data.txt", "w")
file.write("New content\n")
file.close()
# 파일에는 이제 "New content\n"가 들어 있습니다
# 추가 모드 - 기존 내용 유지, 새 내용이 추가됨
file = open("data.txt", "a")
file.write("Added line\n")
file.close()
# 파일에는 이제 "New content\nAdded line\n"가 들어 있습니다27.3.3) 텍스트 인코딩 이해하기
텍스트 파일을 다룰 때 Python은 디스크에 저장된 바이트(bytes)와 프로그램 안의 문자열 문자(characters) 사이를 어떻게 변환할지 알아야 합니다. 이 변환 과정을 인코딩(encoding) 이라고 합니다.
가장 흔한 인코딩은 UTF-8이며, 어떤 언어의 어떤 문자든 표현할 수 있습니다. 이는 Python 3의 기본 인코딩이자 현대 텍스트 파일의 표준입니다.
# UTF-8 인코딩을 명시적으로 지정합니다(대개 기본값이지만)
file = open("data.txt", "r", encoding="utf-8")인코딩이 왜 중요할까요? 악센트 문자가 들어간 이름을 포함한 텍스트 파일을 생각해 보세요:
# 특수 문자가 포함된 파일 쓰기
file = open("names.txt", "w", encoding="utf-8")
file.write("José\n")
file.write("François\n")
file.write("Müller\n")
file.close()
# 다시 읽어오기
file = open("names.txt", "r", encoding="utf-8")
content = file.read()
file.close()
print(content)Output:
José
François
Müller잘못된 인코딩으로 파일을 열려고 하면 문자가 깨져 보이거나 오류가 발생할 수 있습니다. 특별한 이유가 없다면 새 파일에는 항상 UTF-8을 사용하세요.
27.3.4) 추가 모드 변형
Python은 주요 모드와 결합할 수 있는 추가 모드 문자도 제공합니다.
플러스 모드(plus modes) 는 읽기와 쓰기를 모두 허용합니다:
'r+'- 읽기 및 쓰기(파일이 존재해야 함)'w+'- 쓰기 및 읽기(기존 내용 지움)'a+'- 추가 및 읽기(기존 내용 유지)
초보자에게는 플러스 모드보다 파일을 읽기용으로 한 번 열고, 쓰기용으로 따로 여는 방식이 더 명확합니다. 이 장에서는 단순 모드('r', 'w', 'a')만 사용하겠습니다.
27.4) with를 사용해 파일을 자동으로 관리하기
with 문은 파일을 더 깔끔하고 안전하게 다루는 방법을 제공합니다. 작업이 끝나면, 오류가 발생하더라도 자동으로 파일을 닫습니다.
27.4.1) with 문 문법
다음은 with로 파일을 여는 방법입니다:
with open("data.txt", "r") as file:
content = file.read()
print(content)
# 파일은 여기에서 자동으로 닫힙니다이 문법은 여러 부분으로 구성됩니다:
with- 컨텍스트 매니저(context manager)를 시작하는 키워드open("data.txt", "r")- 파일을 엽니다as file- 파일 객체를 참조할 변수를 만듭니다:- 들여쓰기 블록을 시작합니다- 들여쓰기 블록 - 파일로 작업하는 코드
- 블록 이후 - 파일이 자동으로 닫힙니다
핵심 장점은 다음과 같습니다: with 블록이 끝날 때 Python이 파일이 닫히도록 보장한다는 점입니다. 블록이 정상적으로 끝나든, return으로 끝나든, 예외 때문에 끝나든 관계없습니다.
27.4.2) with가 수동 닫기보다 나은 이유
두 접근을 비교해 봅시다:
# 수동 닫기 - 위험함
file = open("data.txt", "r")
content = file.read()
result = process(content) # 여기서 예외가 발생하면...
file.close() # ...이 코드는 실행되지 않습니다
# with 사용 - 안전함
with open("data.txt", "r") as file:
content = file.read()
result = process(content) # 여기서 예외가 발생하더라도...
# ...파일은 여전히 자동으로 닫힙니다with 문은 Python의 컨텍스트 매니저 프로토콜(context manager protocol) 을 사용합니다(28장에서 자세히 살펴보겠습니다). 지금은 이를 다음과 같은 보장으로 생각하면 됩니다: “무슨 일이 있더라도, 작업이 끝나면 이 리소스를 정리하겠다.”
27.4.3) 여러 파일 작업하기
하나의 with 문에서 여러 파일을 열 수 있습니다:
with open("input.txt", "r") as infile, open("output.txt", "w") as outfile:
content = infile.read()
outfile.write(content.upper())
# 두 파일은 여기에서 자동으로 닫힙니다이 방식은 한 파일에서 읽고 다른 파일에 동시에 써야 할 때 유용합니다. 오류가 발생하더라도 두 파일 모두 올바르게 닫히는 것이 보장됩니다.
27.4.4) 파일 객체는 with 블록 안에서만 유효합니다
with 블록이 끝나면 파일은 닫히며, 더 이상 파일 객체를 사용할 수 없습니다:
with open("data.txt", "r") as file:
content = file.read()
print("Inside with block:", file.closed) # Output: Inside with block: False
print("Outside with block:", file.closed) # Output: Outside with block: True
# This raises ValueError: I/O operation on closed file
more_content = file.read()이 동작은 의도된 것입니다. 실수로 닫힌 파일을 사용하지 않도록 방지합니다. with 블록 밖에서 파일 내용을 사용해야 한다면, 블록이 끝나기 전에 위의 content처럼 변수에 저장해 두세요.
이 지점부터는 이 장의 모든 파일 작업에서 with를 사용하겠습니다. 이는 권장되는 접근이며, 여러분의 코드에서도 사용해야 하는 방식입니다.
27.5) 텍스트 파일 읽기
이제 with로 파일을 안전하게 여는 방법을 이해했으니, 텍스트 파일에서 내용을 읽는 다양한 방법을 살펴보겠습니다.
27.5.1) read()로 전체 파일 읽기
read() 메서드는 파일 전체 내용을 하나의 문자열로 읽어옵니다:
with open("message.txt", "r") as file:
content = file.read()
print(content)message.txt에 다음 내용이 들어 있다면:
Welcome to Python!
This is a text file.
It has multiple lines.Output:
Welcome to Python!
This is a text file.
It has multiple lines.read() 메서드는 파일의 모든 줄바꿈 문자(\n)를 포함합니다. 문자열을 print()로 출력하면, 이 줄바꿈 문자 때문에 Python이 각 줄을 별도의 줄로 표시합니다.
또한 read()에 숫자를 전달하면 특정 개수의 문자만 읽을 수도 있습니다:
with open("message.txt", "r") as file:
first_ten = file.read(10) # 처음 10글자 읽기
print(f"First 10 characters: '{first_ten}'")Output:
First 10 characters: 'Welcome to'파일 전체를 읽는 것은 간단하며 작은 파일에 잘 맞습니다. 하지만 큰 파일(메가바이트 또는 기가바이트)에서는 한 번에 전부 읽으면 메모리를 너무 많이 사용할 수 있습니다. 그런 경우에는 한 줄씩 읽는 방식이 더 효율적입니다.
27.5.2) readline()으로 한 줄씩 읽기
readline() 메서드는 파일에서 한 줄을 읽어오며, 끝의 줄바꿈 문자까지 포함합니다:
with open("message.txt", "r") as file:
line1 = file.readline()
line2 = file.readline()
line3 = file.readline()
print(f"Line 1: {line1}")
print(f"Line 2: {line2}")
print(f"Line 3: {line3}")Output:
Line 1: Welcome to Python!
Line 2: This is a text file.
Line 3: It has multiple lines.
출력에 빈 줄이 추가로 들어간 것을 볼 수 있습니다. 파일에서 읽은 각 줄은 \n으로 끝나고, print()가 또 다른 줄바꿈을 추가하기 때문입니다. 이를 피하려면 rstrip() 메서드로 뒤쪽 공백을 제거하세요:
with open("message.txt", "r") as file:
line1 = file.readline().rstrip()
line2 = file.readline().rstrip()
print(f"Line 1: {line1}")
print(f"Line 2: {line2}")Output:
Line 1: Welcome to Python!
Line 2: This is a text file.readline()이 파일 끝에 도달하면 빈 문자열 ""을 반환합니다. 이를 이용해 더 읽을 줄이 없는지 감지할 수 있습니다:
with open("message.txt", "r") as file:
while True:
line = file.readline()
if line == "": # 파일 끝
break
print(line.rstrip())하지만 파일을 한 줄씩 읽는 더 Pythonic한 방법이 있습니다.
27.5.3) for 반복문으로 줄을 순회하기
파일 객체는 이터러블(iterable) 이므로 for 반복문(loop)으로 직접 순회할 수 있습니다. 이것이 파일을 한 줄씩 읽는 가장 일반적이고 Pythonic한 방법입니다:
with open("message.txt", "r") as file:
for line in file:
print(line.rstrip())Output:
Welcome to Python!
This is a text file.
It has multiple lines.이 방식은 다음과 같은 장점이 있습니다:
- 더 깔끔합니다:
readline()이나 빈 문자열 체크가 필요 없습니다 - 더 효율적입니다: Python이 파일을 덩어리(chunk)로 읽어 전체를 한 번에 메모리에 올리지 않습니다
- 더 Pythonic합니다: Python의 핵심 개념인 이터레이션(iteration)을 사용합니다
반복문의 각 순회마다 파일의 다음 줄을 읽습니다. 더 이상 줄이 없으면 반복문이 자동으로 끝납니다.
27.5.4) readlines()로 모든 줄을 리스트로 읽기
readlines() 메서드는 파일의 모든 줄을 읽어 문자열 리스트로 반환합니다:
with open("message.txt", "r") as file:
lines = file.readlines()
print(f"Number of lines: {len(lines)}")
for i, line in enumerate(lines, start=1):
print(f"Line {i}: {line.rstrip()}")Output:
Number of lines: 3
Line 1: Welcome to Python!
Line 2: This is a text file.
Line 3: It has multiple lines.리스트의 각 원소는 줄바꿈 문자를 포함한 한 줄짜리 문자열입니다. 이는 다음이 필요할 때 유용합니다:
- 인덱스로 줄에 접근:
lines[0],lines[1]등 - 여러 번 줄을 처리
- 처리 전에 전체 줄 수를 파악
하지만 read()와 마찬가지로 readlines()도 파일 전체를 메모리에 올립니다. 큰 파일에서는 for 반복문으로 순회하는 방식이 메모리 측면에서 더 효율적입니다.
27.6) 텍스트 파일에 쓰기와 추가하기
파일에 쓰는 작업은 읽는 것만큼 중요합니다. Python은 새 파일을 만들거나 기존 파일을 수정하는 간단한 방법을 제공합니다.
27.6.1) write()로 파일에 쓰기
파일에 쓰려면 쓰기 모드('w')로 열고 write() 메서드를 사용합니다:
with open("output.txt", "w") as file:
file.write("Hello, World!\n")
file.write("This is a new file.\n")이 코드를 실행한 뒤 output.txt에는 다음이 들어 있습니다:
Hello, World!
This is a new file.write()의 중요한 점:
- 파일에 문자열을 씁니다
- 줄바꿈을 자동으로 추가하지 않습니다—
\n을 직접 포함해야 합니다 - 작성된 문자 수를 반환합니다(하지만 보통은 무시합니다)
- 파일이 이미 존재하면, 쓰기 모드는 쓰기 전에 기존 내용을 모두 지웁니다
기존 파일에 쓰면 어떤 일이 일어나는지 보겠습니다:
# 먼저 어떤 내용이 있는 파일을 만듭니다
with open("demo.txt", "w") as file:
file.write("Original content\n")
# 이제 다시 쓰기 모드로 엽니다
with open("demo.txt", "w") as file:
file.write("New content\n")
# 파일을 읽어 무엇이 들어 있는지 확인합니다
with open("demo.txt", "r") as file:
print(file.read())Output:
New content원래 내용은 사라졌습니다. 쓰기 모드는 새 파일을 만들든 기존 파일을 덮어쓰든 항상 빈 파일에서 시작합니다.
27.6.2) 여러 줄 쓰기
write()를 여러 번 호출해 여러 줄을 쓸 수 있습니다:
with open("shopping_list.txt", "w") as file:
file.write("Apples\n")
file.write("Bananas\n")
file.write("Oranges\n")27.6.3) 컬렉션의 데이터를 쓰기
흔히 하는 작업 중 하나는 리스트(list) 같은 컬렉션의 데이터를 파일에 쓰는 것입니다:
students = ["Alice", "Bob", "Carol", "David"]
with open("students.txt", "w") as file:
for student in students:
file.write(student + "\n")이 코드는 다음 내용의 students.txt를 생성합니다:
Alice
Bob
Carol
David27.6.4) 파일에 추가하기
이미 있는 내용을 지우지 않고 기존 파일의 끝에 내용을 추가하려면 추가 모드('a')를 사용합니다:
# 초기 내용이 있는 파일을 만듭니다
with open("log.txt", "w") as file:
file.write("Program started\n")
# 나중에 더 많은 내용을 추가합니다
with open("log.txt", "a") as file:
file.write("Processing data\n")
file.write("Processing complete\n")
# 파일을 읽어 모든 내용을 확인합니다
with open("log.txt", "r") as file:
print(file.read())Output:
Program started
Processing data
Processing complete추가 모드는 로그 파일에 완벽합니다. 이벤트 기록을 계속 유지하고 싶기 때문입니다. 추가 모드로 파일을 열 때마다 새 내용이 끝에 붙고, 기존 내용은 그대로 보존됩니다.
27.7) 흔한 파일 I/O 오류 처리하기
파일 작업은 여러 이유로 실패할 수 있습니다. 파일이 존재하지 않을 수도 있고, 접근 권한이 없을 수도 있으며, 디스크 공간이 부족할 수도 있고, 파일이 다른 프로그램에서 이미 열려 있을 수도 있습니다. 이런 오류를 우아하게 처리하는 법을 배우면 프로그램이 더 견고하고 사용자 친화적이 됩니다.
27.7.1) FileNotFoundError: 파일이 존재하지 않을 때
가장 흔한 파일 오류는 존재하지 않는 파일을 읽으려 할 때 발생합니다:
# WARNING: data.txt가 없으면 FileNotFoundError가 발생합니다
with open("data.txt", "r") as file:
content = file.read()data.txt가 없으면 Python은 다음을 발생시킵니다:
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'이를 우아하게 처리하려면 try-except 블록을 사용하세요(25장에서 배운 내용입니다):
try:
with open("data.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("Error: The file 'data.txt' was not found.")
print("Please check the filename and try again.")Output (파일이 없을 때):
Error: The file 'data.txt' was not found.
Please check the filename and try again.이 접근은 프로그램이 크래시 나는 것을 막고, 사용자에게 도움이 되는 메시지를 제공합니다.
27.7.2) PermissionError: 파일에 접근할 수 없을 때
때로는 파일을 읽거나 쓸 권한이 없을 수 있습니다:
try:
with open("/root/protected.txt", "r") as file:
content = file.read()
except PermissionError:
print("Error: You don't have permission to access this file.")권한 오류는 다음 상황에서 발생할 수 있습니다:
- 파일이 다른 사용자의 소유일 때
- 파일이 보호된 시스템 디렉터리에 있을 때
- 파일이 읽기 전용으로 표시되어 있는데 쓰려고 할 때
- Windows에서 파일이 다른 프로그램에 의해 열려 있을 때
27.7.3) IsADirectoryError: 디렉터리를 열려고 할 때
실수로 파일이 아니라 디렉터리를 열려고 하면:
try:
with open("my_folder", "r") as file:
content = file.read()
except IsADirectoryError:
print("Error: 'my_folder' is a directory, not a file.")이런 일은 비슷한 이름의 파일과 디렉터리가 둘 다 있거나, 경로에서 파일 이름을 포함하는 것을 잊었을 때 발생할 수 있습니다.
27.7.4) UnicodeDecodeError: 인코딩이 맞지 않을 때
잘못된 인코딩으로 파일을 읽으려 하면 UnicodeDecodeError가 발생할 수 있습니다:
try:
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()
except UnicodeDecodeError:
print("Error: The file encoding doesn't match UTF-8.")
print("The file might use a different encoding.")이 오류는 파일에 UTF-8로 유효하지 않은 바이트가 포함되어 있을 때 발생합니다. 이를 마주한다면 파일은 다음일 수 있습니다:
- 다른 인코딩(예: Latin-1 또는 Windows-1252)을 사용 중
- 텍스트로 읽으려는 바이너리 파일
- 손상됨
27.7.5) 여러 오류 유형 처리하기
하나의 try-except 블록에서 여러 오류 유형을 잡을 수 있습니다:
filename = input("Enter filename: ")
try:
with open(filename, "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print(f"Error: '{filename}' does not exist.")
except PermissionError:
print(f"Error: You don't have permission to read '{filename}'.")
except IsADirectoryError:
print(f"Error: '{filename}' is a directory, not a file.")
except UnicodeDecodeError:
print(f"Error: '{filename}' contains invalid text encoding.")이렇게 하면 각 문제 유형에 대해 구체적이고 도움이 되는 오류 메시지를 제공할 수 있습니다. 사용자는 무엇이 잘못됐는지 정확히 알고 적절한 조치를 취할 수 있습니다.
27.7.6) 모든 예외를 잡는 처리기 사용하기
때로는 우리가 다룬 특정 유형 외에도 예기치 않은 파일 관련 오류를 모두 잡고 싶을 수 있습니다. 구체적인 처리기 뒤에 일반적인 Exception 처리기를 캐치올(catch-all)로 둘 수 있습니다:
filename = input("Enter filename: ")
try:
with open(filename, "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print(f"Error: '{filename}' not found.")
except PermissionError:
print(f"Error: No permission to read '{filename}'.")
except IsADirectoryError:
print(f"Error: '{filename}' is a directory.")
except UnicodeDecodeError:
print(f"Error: '{filename}' has invalid encoding.")
except Exception as e:
print(f"Unexpected error reading file: {e}")이렇게 하면 예상하지 못한 오류까지도 프로그램이 처리하도록 보장합니다. 변수 e는 예외 객체를 담고 있으며, 여기에는 설명적인 오류 메시지가 포함됩니다. 이를 출력하면 사용자에게 어떤 문제가 발생했는지에 대한 기술적인 세부 정보를 제공합니다.
파일을 다루는 것은 프로그래밍의 기본 기술입니다. 여러분은 다음을 배웠습니다:
- 파일 경로와 현재 작업 디렉터리를 이해하기
- 파일을 올바르게 열고 닫기
- 읽기, 쓰기, 추가를 위한 다양한 파일 모드 사용하기
with문으로 파일을 자동으로 관리하기- 다양한 방법으로 파일 읽기(
read(),readline(), 순회,readlines()) - 파일에 내용 쓰기와 추가하기
- 흔한 파일 I/O 오류를 우아하게 처리하기
이 기술들은 실행 사이에도 데이터를 유지하는 프로그램을 만들고, 텍스트 파일을 처리하고, 보고서를 생성하는 등 훨씬 더 많은 일을 가능하게 합니다. 다음 장에서는 컨텍스트 매니저(context manager)를 깊이 있게 살펴보면서, with 문이 동작하게 만드는 메커니즘과 파일을 넘어 다양한 리소스를 관리하기 위한 사용자 정의 컨텍스트 매니저를 만드는 방법을 이해하겠습니다.