Python 강좌 : 제 25강 - 파일 읽기 & 쓰기
파일 읽기 & 쓰기(File Read & Write)
파일 열기 함수(open)는 대부분의 파일을 읽고 쓰기가 가능한 함수입니다.
파일 열기 함수는 텍스트 파일뿐만 아니라 이미지와 같은 이진(binary) 파일도 다룰 수 있지만, 주로 텍스트 파일을 대상으로 사용합니다.
open과 close를 이용하여 텍스트 파일을 다룰 수 있습니다.
지금까지 변수에 저장한 값은 모두 메모리에 있으므로, 프로그램이 종료되면 사라집니다. 계산 결과를 보관하거나, 다른 프로그램이 만든 데이터를 읽어 오거나, 설정을 저장해 두려면 값을 디스크의 파일로 옮겨야 합니다.
파일을 다루는 과정은 항상 열기(open) → 읽기 또는 쓰기 → 닫기(close)의 세 단계로 이루어집니다.
파일을 열면 Python은 운영체제에 파일 사용을 요청하고, 그 파일을 다룰 수 있는 파일 객체(file object)를 돌려줍니다. 이후의 읽기와 쓰기는 모두 이 파일 객체의 메서드로 수행합니다.
이번 강좌에서는 파일을 여는 방법과 모드, 파일 객체가 현재 위치(커서)를 기억하며 읽고 쓰는 방식, 쓴 내용이 바로 디스크에 저장되지 않는 버퍼링, 그리고 with 문이 파일을 안전하게 닫아 주는 원리까지 알아봅니다.
텍스트 파일 열기 & 닫기
file = open("textfile.txt", mode="r", encoding="utf-8")
file.close()텍스트 파일을 읽거나 쓰기 위해선 파일의 경로를 미리 지정해야 합니다.
파일 열기 함수(open)을 사용해 파일을 열 수 있습니다.
변수 = open(경로, 모드, encoding=인코딩)을 의미합니다.
경로는 파일을 작성하거나 읽을 경로를 의미합니다.
모드는 파일을 어떻게 사용할지를 설정합니다.
인코딩은 텍스트 파일의 문자를 어떤 방식으로 저장하고 해석할지를 설정합니다.
- Tip : 위 예제는
읽기 모드(r)이므로,textfile.txt파일이 없다면FileNotFoundError가 발생합니다. 아래의 텍스트 파일 쓰기를 먼저 실행해 파일을 생성합니다.
파일 열기 함수 모드 플래그
| 플래그 | 의미 |
|---|---|
| r | 읽기 전용(기본값) |
| w | 쓰기 전용 (파일이 있으면 내용을 지움) |
| a | 내용 추가 (파일 끝에 이어서 작성) |
| x | 새 파일 생성 후 쓰기 (파일이 있으면 오류) |
| + | 읽기와 쓰기를 함께 사용 |
| t | 텍스트 모드(기본값) |
| b | 이진 모드 |
모드는 크게 r, w, a, x의 네 가지와 t, b의 두 가지로 성격을 나눌 수 있습니다.
파일을 어떻게 사용할지 선택(r, w, a, x)하고, 어떤 방식으로 다룰지 선택(t, b)하게 됩니다.
어떻게 사용할지를 생략하면 r로 설정되며, 어떤 방식으로 다룰지를 생략하면 t로 설정됩니다.
예를 들어, mode="r"로 사용한다면, 텍스트 파일로 읽는다는 의미가 되며, mode="rb"는 이진 파일로 읽는다는 의미가 됩니다.
텍스트 모드는 파일의 바이트를 지정한 인코딩으로 해석하여 문자열(str)로 주고받으며, 이진 모드는 해석하지 않고 바이트(bytes) 그대로 주고받습니다.
예제는 open("textfile.txt", mode="r", encoding="utf-8")로 사용하므로, 현재 작업 경로의 textfile.txt를 읽습니다.
경로는 textfile.txt와 같은 상대 경로나 D:/textfile.txt와 같은 절대 경로로 작성할 수 있습니다.
상대 경로는 소스 코드 파일의 위치가 아니라 프로그램을 실행한 위치(현재 작업 경로)를 기준으로 합니다. 같은 코드라도 실행한 위치에 따라 찾는 파일이 달라질 수 있으므로, 파일을 찾지 못한다면 현재 작업 경로부터 확인합니다.
변수명.close()를 사용하여 파일을 닫습니다.
파일을 닫지 않으면, 파일이 열린 상태가 유지됩니다.
프로그램이 종료될 때 파일이 자동으로 닫히지만, 닫지 않고 사용할 경우 작성한 내용이 디스크에 기록되지 않거나 다른 프로그램에서 파일을 사용하지 못하는 문제가 발생할 수 있습니다.
또한, 운영체제는 한 프로그램이 동시에 열 수 있는 파일의 수를 제한하므로, 반복문 안에서 파일을 열고 닫지 않으면 이 제한에 도달할 수 있습니다.
file = open("object.txt", mode="w", encoding="utf-8")
print(type(file))
print(file.name, file.mode, file.encoding)
print(file.closed)
file.close()
print(file.closed)- 결과
- <class ‘_io.TextIOWrapper’>
object.txt w utf-8
False
True
텍스트 모드로 연 파일 객체는 TextIOWrapper 형식입니다. 이름처럼 바이트를 다루는 하위 객체를 감싸서(wrap) 문자열 단위로 읽고 쓸 수 있게 해 주는 객체입니다.
파일 객체는 열 때 지정한 name(경로), mode(모드), encoding(인코딩)을 속성으로 가지고 있습니다.
closed 속성은 파일이 닫혔는지를 나타냅니다. close()를 호출하기 전에는 False, 호출한 뒤에는 True가 됩니다.
file = open("object.txt", mode="w", encoding="utf-8")
file.close()
try:
file.write("Hello")
except ValueError as e:
print(e)- 결과
- I/O operation on closed file.
닫힌 파일 객체는 변수에 그대로 남아 있지만, 더 이상 읽거나 쓸 수 없습니다. 닫힌 파일에 write()나 read()를 호출하면 ValueError가 발생합니다.
try:
open("nofile.txt", mode="r", encoding="utf-8")
except FileNotFoundError as e:
print(e)- 결과
- [Errno 2] No such file or directory: ‘nofile.txt’
읽기 모드로 존재하지 않는 파일을 열면 FileNotFoundError가 발생합니다. 오류 메시지에 Python이 찾으려 한 경로가 표시되므로, 경로 오타나 현재 작업 경로를 확인할 수 있습니다.
파일 객체와 커서(cursor)
파일 객체는 파일의 어디까지 읽었는지(또는 썼는지) 위치를 기억하고 있습니다. 이 위치를 파일 포인터 또는 커서(cursor)라 합니다.
read()나 readline()은 커서 위치부터 읽은 뒤 읽은 만큼 커서를 뒤로 이동시킵니다. 그러므로 같은 메서드를 다시 호출하면 이어지는 내용을 읽게 됩니다.
with open("cursor.txt", mode="w", encoding="utf-8") as file:
file.write("ABCDEFGHIJ")
with open("cursor.txt", mode="r", encoding="utf-8") as file:
print(file.tell())
print(file.read(3))
print(file.tell())
print(file.read(4))
print(file.tell())
print(repr(file.read()))
print(repr(file.read()))
file.seek(0)
print(file.read(2))- 결과
- 0
ABC
3
DEFG
7
‘HIJ’
‘‘
AB
tell()은 현재 커서 위치를, seek(위치)는 커서를 지정한 위치로 이동시킵니다.
결과를 한 줄씩 읽어 보면 다음과 같습니다.
- 파일을 막 열었으므로 커서는 맨 앞인
0입니다. read(3)은 커서 위치부터 3글자를 읽어ABC를 반환하고, 커서는3으로 이동합니다.read(4)는 이어서DEFG를 읽고, 커서는7로 이동합니다.read()처럼 크기를 생략하면 커서부터 끝까지 읽으므로'HIJ'가 반환됩니다.- 커서가 이미 끝에 있으므로 다시
read()를 호출하면 빈 문자열''이 반환됩니다. seek(0)으로 커서를 맨 앞으로 되돌리면 처음부터 다시 읽을 수 있습니다.
with open("korean.txt", mode="w", encoding="utf-8") as file:
file.write("파이썬")
with open("korean.txt", mode="r", encoding="utf-8") as file:
print(file.read(1))
print(file.tell())- 결과
- 파
3
텍스트 모드의 read(n)은 문자 n개를 읽지만, tell()이 반환하는 위치는 문자 수가 아닌 바이트 기준의 값입니다.
UTF-8에서 한글 한 글자는 3바이트로 저장되므로, "파" 한 글자를 읽은 뒤의 위치는 1이 아닌 3이 됩니다.
그러므로, 텍스트 모드에서 seek()에는 0이나 tell()로 얻은 값만 사용합니다. 임의의 숫자로 이동하면 한글 한 글자의 중간으로 이동하여 문자를 올바르게 해석하지 못할 수 있습니다.
with open("lines.txt", mode="w", encoding="utf-8") as file:
file.write("A\nB\nC\n")
with open("lines.txt", mode="r", encoding="utf-8") as file:
lines = file.readlines()
again = file.read()
print(lines)
print(repr(again))
file.seek(0)
print(repr(file.read()))- 결과
- [‘A\n’, ‘B\n’, ‘C\n’]
‘‘
‘A\nB\nC\n’
커서 때문에 생기는 가장 흔한 실수입니다. readlines()로 이미 끝까지 읽은 파일 객체에서 다시 read()를 호출하면 빈 문자열이 반환됩니다.
seek(0)으로 커서를 맨 앞으로 되돌린 뒤에는 다시 처음부터 전체 내용을 읽을 수 있습니다.
파일 객체는 제 22강의 반복자처럼 앞으로만 진행하므로, 같은 내용을 다시 읽으려면 seek(0)으로 되돌리거나, 처음 읽은 결과를 변수에 저장해 사용합니다.
버퍼링(buffering)
디스크에 쓰는 작업은 메모리에 쓰는 것보다 매우 느립니다. 그래서 파일 객체는 write()로 받은 내용을 바로 디스크에 쓰지 않고, 메모리의 버퍼(buffer)에 모아 두었다가 한꺼번에 내보냅니다.
작은 내용을 여러 번 쓰더라도 실제 디스크 작업은 적은 횟수로 처리되므로 속도가 빨라집니다.
import io
from pathlib import Path
print(io.DEFAULT_BUFFER_SIZE)
file = open("buffer.txt", mode="w", encoding="utf-8")
file.write("Hello")
print(repr(Path("buffer.txt").read_text(encoding="utf-8")))
file.flush()
print(repr(Path("buffer.txt").read_text(encoding="utf-8")))
file.close()- 결과
- 131072
‘‘
‘Hello’
io.DEFAULT_BUFFER_SIZE는 기본 버퍼의 크기이며, Python 3.14.6에서 확인한 값은 131072 바이트(128KiB)입니다.
file.write("Hello")를 호출한 직후 같은 파일을 다른 방법으로 읽어 보면 빈 문자열입니다. "Hello"가 아직 버퍼에만 있고 파일에는 기록되지 않았기 때문입니다.
flush()를 호출하면 버퍼의 내용이 즉시 파일로 내보내지므로, 다시 읽었을 때 'Hello'가 보입니다.
버퍼의 내용은 버퍼가 가득 찼을 때, flush()를 호출했을 때, close()로 파일을 닫을 때 파일로 기록됩니다.
그러므로, 파일을 닫지 않은 채 프로그램이 비정상적으로 종료되면 버퍼에 남아 있던 내용을 잃을 수 있습니다. 파일을 반드시 닫아야 하는 가장 큰 이유가 이것입니다.
- Tip : 로그처럼 다른 프로그램이 실시간으로 읽어야 하는 파일이라면, 중요한 내용을 쓴 뒤
flush()를 호출합니다.
인코딩(encoding)
컴퓨터의 파일에는 문자가 아닌 바이트(숫자)가 저장됩니다. 인코딩(encoding)은 문자를 어떤 바이트로 바꿔 저장할지 정한 규칙이며, 파일을 쓸 때와 읽을 때 같은 규칙을 사용해야 원래의 문자로 되돌릴 수 있습니다.
import locale
print(locale.getencoding())
with open("textfile.txt", mode="r") as file:
print(file.encoding)
with open("textfile.txt", mode="r", encoding="utf-8") as file:
print(file.encoding)- 결과
- cp949
cp949
utf-8
텍스트 모드에서 encoding을 생략하면 운영체제의 지역 설정(locale)에 따른 기본 인코딩이 사용됩니다.
위 결과는 한국어 Windows에서 Python 3.14.6으로 실행한 결과이며, 기본 인코딩이 cp949로 설정되는 것을 확인할 수 있습니다.
macOS나 Linux에서는 대부분 utf-8이 기본값이므로, 같은 코드라도 운영체제에 따라 다른 인코딩으로 파일을 읽고 쓰게 됩니다.
그러므로, encoding="utf-8"과 같이 인코딩을 명시하는 것을 권장합니다.
-
Tip :
locale.getencoding()은 Python 3.11 이상에서 사용할 수 있습니다. -
Tip :
python -X warn_default_encoding으로 실행하면, 인코딩을 생략한open호출에서EncodingWarning경고를 출력합니다. (Python 3.10 이상) -
Tip : PEP 686에 따라 Python 3.15부터 UTF-8 모드가 기본값이 될 예정입니다. 그 이전 버전에서도 동일하게 동작하도록 인코딩을 명시합니다.
with open("enc.txt", mode="w", encoding="utf-8") as file:
file.write("파이썬")
try:
with open("enc.txt", mode="r", encoding="cp949") as file:
print(file.read())
except UnicodeDecodeError as e:
print(e)
with open("enc.txt", mode="rb") as file:
print(file.read())- 결과
- ‘cp949’ codec can’t decode byte 0xed in position 0: illegal multibyte sequence
b’\xed\x8c\x8c\xec\x9d\xb4\xec\x8d\xac’
UTF-8로 저장한 파일을 cp949로 읽으면, 같은 바이트를 다른 규칙으로 해석하려 하므로 UnicodeDecodeError가 발생합니다.
이진 모드(rb)로 열어 보면 파일에 실제로 저장된 바이트를 확인할 수 있습니다. "파이썬" 세 글자가 UTF-8에서 글자당 3바이트씩, 모두 9바이트로 저장되어 있습니다.
인코딩이 맞지 않으면 이처럼 오류가 발생하거나, 오류 없이 깨진 글자로 읽히기도 하므로, 파일을 만든 쪽의 인코딩을 확인하여 같은 인코딩으로 엽니다.
파일 자동 닫기
with open("textfile.txt", encoding="utf-8") as file:
...파일을 읽거나 쓸 때, close 메서드를 사용해 명시적으로 닫아야 합니다.
하지만, with 키워드를 사용한다면 들여쓰기 구문을 벗어났을 때 자동으로 파일이 닫힙니다.
구문 안에서 오류가 발생하더라도 파일은 자동으로 닫힙니다.
as 키워드는 열린 파일의 변수명을 설정합니다.
들여쓰기 간격으로 파일이 언제 열리고 닫히는지 확인할 수 있으므로, 코드를 더 효율적으로 관리할 수 있습니다.
file.close()를 직접 호출하는 방식은 close()까지 실행이 도달해야만 파일이 닫힙니다. 중간에 오류가 발생하거나 return으로 함수를 빠져나가면 close()가 실행되지 않고 파일이 열린 채로 남습니다.
이를 막으려면 다음과 같이 try-finally 구문으로 감싸야 합니다. finally 블록은 오류가 발생하든 발생하지 않든 항상 실행됩니다.
file = open("object.txt", mode="w", encoding="utf-8")
try:
file.write("Hello")
finally:
file.close()
print(file.closed)- 결과
- True
with 문은 이 try-finally 구조를 자동으로 만들어 주는 문법입니다.
with 문에 사용할 수 있는 객체를 컨텍스트 관리자(Context manager)라 하며, 블록에 들어갈 때 호출되는 __enter__ 메서드와 블록을 벗어날 때 호출되는 __exit__ 메서드를 가지고 있습니다.
파일 객체의 __enter__는 파일 객체 자신을 반환하여 as 뒤의 변수에 할당하고, __exit__는 블록을 어떤 방식으로 벗어나든 파일을 닫습니다. 정상 종료, 오류 발생, return, break 모두 해당합니다.
try:
with open("with_test.txt", mode="w", encoding="utf-8") as file:
file.write("저장 중")
raise ValueError("작업 중 오류 발생")
except ValueError as e:
print(e)
print(file.closed)
with open("with_test.txt", mode="r", encoding="utf-8") as file:
print(file.read())- 결과
- 작업 중 오류 발생
True
저장 중
with 블록 안에서 raise로 오류를 일부러 발생시켰습니다.
오류가 발생해 블록을 빠져나가는 순간 __exit__가 호출되어 파일이 닫혔으므로 file.closed는 True입니다.
파일이 닫히면서 버퍼의 내용도 기록되었으므로, 오류가 발생하기 전에 쓴 "저장 중"을 다시 읽을 수 있습니다.
__exit__는 오류를 처리하지 않고 그대로 바깥으로 전달하므로, 오류 메시지는 바깥의 try-except에서 출력됩니다.
- Tip :
with open("a.txt") as a, open("b.txt", "w") as b:처럼 쉼표로 구분하여 여러 파일을 한 번에 열 수 있으며, 블록을 벗어나면 모두 닫힙니다.
텍스트 파일 쓰기
with open("textfile.txt", mode="w", encoding="utf-8") as file:
words = ["Python\n", "YUNDAEHEE\n", "076923\n"]
file.write("START\n")
file.writelines(words)
file.write("END")- 결과
- START
Python
YUNDAEHEE
076923
END
예제는 open("textfile.txt", mode="w", encoding="utf-8")로 사용하므로, 현재 작업 경로에 textfile.txt가 생성됩니다.
결과는 생성된 textfile.txt의 내용입니다.
w 모드는 파일이 이미 존재한다면 기존 내용을 모두 지우고 새로 작성합니다.
변수명.write("내용")을 사용하면 텍스트 파일에 단일 문자열을 작성할 수 있습니다.
변수명.writelines(반복 가능한 객체)을 사용하면 텍스트 파일에 문자열 목록을 순차적으로 작성할 수 있습니다.
쓰기도 커서 위치에서 이루어지며, 쓴 만큼 커서가 뒤로 이동합니다. 그러므로 write()를 여러 번 호출하면 앞의 내용 뒤에 이어서 기록됩니다.
-
Tip :
writelines는 줄 바꿈 문자를 자동으로 추가하지 않으므로, 각 문자열에\n을 포함해야 합니다. -
Tip :
write는 문자열만 받으므로, 숫자를 쓰려면file.write(str(123))처럼 문자열로 변환합니다.
with open("count.txt", mode="w", encoding="utf-8") as file:
print(file.write("Python\n"))
print(file.write("파이썬"))- 결과
- 7
3
텍스트 모드의 write()는 기록한 문자의 개수를 반환합니다.
"Python\n"은 줄 바꿈 문자를 포함해 7글자이고, "파이썬"은 3글자입니다. 파일에 저장되는 바이트 수(한글은 글자당 3바이트)와는 다르다는 점에 주의합니다.
with open("newline.txt", mode="w", encoding="utf-8") as file:
file.write("A\nB\n")
with open("newline.txt", mode="rb") as file:
print(file.read())
with open("newline.txt", mode="r", encoding="utf-8") as file:
print(repr(file.read()))- 결과
- b’A\r\nB\r\n’
‘A\nB\n’
위 결과는 Windows에서 실행한 결과입니다.
텍스트 모드로 쓸 때 \n은 운영체제의 줄 바꿈 문자로 변환되어 저장됩니다. Windows의 줄 바꿈은 \r\n이므로, 이진 모드로 읽어 보면 \r\n으로 저장된 것을 확인할 수 있습니다. (macOS와 Linux는 \n 그대로 저장됩니다.)
텍스트 모드로 읽을 때는 \r\n, \r, \n을 모두 \n으로 바꿔서 반환하므로, 코드에서는 운영체제와 관계없이 \n만 신경 쓰면 됩니다.
- Tip : 줄 바꿈 변환을 하지 않으려면
open(경로, mode="w", encoding="utf-8", newline="")처럼newline=""을 지정합니다.csv모듈로 파일을 쓸 때 이 방식을 사용합니다.
텍스트 파일 한 줄씩 읽기
with open("textfile.txt", mode="r", encoding="utf-8") as file:
content = list()
while True:
sentence = file.readline()
if sentence:
content.append(sentence)
else:
break
print(content)
with open("textfile.txt", mode="r", encoding="utf-8") as file:
content = list()
for f in file:
content.append(f)
print(content)- 결과
- [‘START\n’, ‘Python\n’, ‘YUNDAEHEE\n’, ‘076923\n’, ‘END’]
[‘START\n’, ‘Python\n’, ‘YUNDAEHEE\n’, ‘076923\n’, ‘END’]
with 키워드를 활용해 현재 작업 경로에 저장되어 있는 textfile.txt를 불러옵니다.
텍스트 파일을 한 줄씩 읽는 방법은 크게 두 가지가 있습니다.
첫 번째 방식은 while 문을 활용한 방법입니다.
readline() 메서드는 파일의 텍스트를 한 줄씩 불러옵니다.
readline() 메서드는 다음 번 호출 때 자동적으로 다음 줄의 텍스트를 불러옵니다.
다음 줄을 불러올 수 있는 이유는 앞에서 살펴본 커서가 방금 읽은 줄의 끝으로 이동해 있기 때문입니다.
파일의 끝에 도달하면 빈 문자열("")을 반환하므로, 불러온 문장이 아무 것도 없다면 반복을 종료합니다.
내용이 없는 빈 줄은 ""가 아닌 "\n"으로 반환되므로, 파일 중간의 빈 줄 때문에 반복이 일찍 끝나지는 않습니다.
두 번째 방식은 for 문을 활용한 방법입니다.
file 변수는 반복 가능한 객체이므로, 반복할 때마다 한 줄씩 문자열을 불러옵니다.
만약, 더 이상 불러올 문자열이 없다면, 반복이 종료됩니다.
파일 전체를 메모리에 올리지 않으므로, 큰 파일을 다룰 때 효율적입니다.
파일 객체는 제 22강과 제 23강에서 다룬 반복자이므로, for 문뿐만 아니라 map, filter, zip 등에도 그대로 전달할 수 있습니다.
- Tip :
\n은 개행 문자로, 줄 바꿈을 의미합니다.
with open("textfile.txt", mode="r", encoding="utf-8") as file:
for line in file:
print(repr(line), repr(line.rstrip("\n")))- 결과
- ‘START\n’ ‘START’
‘Python\n’ ‘Python’
‘YUNDAEHEE\n’ ‘YUNDAEHEE’
‘076923\n’ ‘076923’
‘END’ ‘END’
한 줄씩 읽은 문자열에는 줄 끝의 \n이 포함되어 있습니다. 마지막 줄 'END'는 파일 끝에 줄 바꿈을 쓰지 않았으므로 \n이 없습니다.
\n이 포함된 채로 print()하면 줄 바꿈이 두 번 출력되거나, 문자열 비교(line == "END")가 실패하는 원인이 됩니다.
그러므로, 줄 단위로 처리할 때는 rstrip("\n")으로 줄 끝의 개행 문자를 제거한 뒤 사용합니다. 줄 끝의 공백까지 제거하려면 인수 없이 rstrip()을 사용합니다.
텍스트 파일 모두 읽기
with open("textfile.txt", mode="r", encoding="utf-8") as file:
lines = file.readlines()
print(lines)
with open("textfile.txt", mode="r", encoding="utf-8") as file:
lines = file.read()
print(lines)- 결과
- [‘START\n’, ‘Python\n’, ‘YUNDAEHEE\n’, ‘076923\n’, ‘END’]
START
Python
YUNDAEHEE
076923
END
텍스트 파일을 모두 읽는 방법은 한 줄씩 읽는 방법과 동일하게 두 가지가 있습니다.
첫 번째 방식은 변수명.readlines()을 이용하여 목록 형식으로 모두 불러오는 방법입니다.
readline에서 s가 추가되어 readlines로 사용합니다.
두 번째 방식은 변수명.read()를 이용해 문자열 형식으로 모두 불러오는 방법입니다.
하나의 문자열에 개행 문자인 \n이 그대로 포함되어 있으므로, 출력하면 줄 바꿈된 형태로 표시됩니다.
위 방식을 이용하면 파일 내의 모든 문자열을 한 번에 불러와 변수에 저장할 수 있습니다.
단, 두 방식 모두 파일 전체를 한 번에 메모리에 올리므로, 크기가 수 GB인 파일에 사용하면 메모리가 부족해질 수 있습니다.
파일이 작고 내용을 여러 번 사용해야 한다면 read()나 readlines()를, 파일이 크거나 한 줄씩 순서대로 처리하면 된다면 for line in file:을 사용합니다.
텍스트 파일 내용 추가하기
with open("textfile.txt", mode="a", encoding="utf-8") as file:
file.write("\nAPPEND")
with open("textfile.txt", mode="r", encoding="utf-8") as file:
print(file.read().splitlines())- 결과
- [‘START’, ‘Python’, ‘YUNDAEHEE’, ‘076923’, ‘END’, ‘APPEND’]
a 모드는 기존 내용을 유지한 채 파일의 끝에 내용을 이어서 작성합니다.
a 모드로 열면 커서가 파일의 끝에 놓이므로, 기존 내용 뒤에 새로운 내용이 기록됩니다. 파일이 없다면 새로 생성합니다.
기존 파일의 마지막 줄 END에는 줄 바꿈이 없으므로, 새 줄로 시작하기 위해 "\nAPPEND"처럼 앞에 \n을 붙였습니다.
문자열.splitlines()를 사용하면 개행 문자를 제외한 줄 단위 목록을 얻을 수 있습니다.
- Tip : 실행 기록(로그)처럼 내용을 계속 누적해야 하는 파일은
a모드로 엽니다. 실수로w모드를 사용하면 지금까지의 기록이 모두 지워집니다.
with open("textfile.txt", mode="x", encoding="utf-8") as file:
file.write("X")- 결과
- FileExistsError: [Errno 17] File exists: ‘textfile.txt’
x 모드는 새로운 파일을 생성할 때만 사용할 수 있습니다.
이미 파일이 존재한다면 FileExistsError가 발생하므로, 기존 파일을 실수로 덮어쓰는 것을 방지할 수 있습니다.
w 모드는 파일의 존재 여부와 관계없이 내용을 지우지만, x 모드는 파일이 없을 때만 쓰기를 허용한다는 차이가 있습니다.
pathlib으로 파일 읽기 & 쓰기
from pathlib import Path
path = Path("pathfile.txt")
size = path.write_text("파이썬\n076923\n", encoding="utf-8")
print(size)
text = path.read_text(encoding="utf-8")
print(text.splitlines())
print(path.exists(), path.name, path.suffix)- 결과
- 11
[‘파이썬’, ‘076923’]
True pathfile.txt .txt
pathlib 모듈의 Path 클래스를 사용하면 경로를 객체로 다룰 수 있습니다.
Path.write_text(문자열, encoding=인코딩)은 파일을 열고, 문자열을 쓰고, 닫는 과정을 한 번에 수행합니다.
반환값은 작성한 문자의 개수입니다.
예제의 "파이썬\n076923\n"은 한글 3글자, 줄 바꿈 2개, 숫자 6개로 모두 11글자이므로 11이 반환됩니다.
Path.read_text(encoding=인코딩)은 파일의 모든 내용을 문자열로 불러옵니다.
write_text는 w 모드와 동일하게 기존 내용을 덮어씁니다.
두 메서드는 내부에서 파일을 열고 바로 닫으므로, with 문을 작성하지 않아도 파일이 열린 채로 남지 않습니다. 파일 전체를 한 번에 읽고 쓰는 간단한 작업에 적합합니다.
Path 객체는 exists()로 파일의 존재 여부를 확인하거나, name, suffix 속성으로 파일 이름과 확장자를 확인할 수 있습니다.
또한, Path("folder") / "textfile.txt"와 같이 / 연산자로 경로를 결합할 수 있습니다.
- Tip : 이진 파일은
Path.read_bytes()와Path.write_bytes(데이터)로 다룰 수 있습니다.
from pathlib import Path
path = Path("data") / "logs" / "app.txt"
print(path)
print(path.parent, path.stem, path.suffix)- 결과
- data\logs\app.txt
data\logs app .txt
/ 연산자로 결합한 경로는 운영체제에 맞는 구분자로 표시됩니다. 위 결과는 Windows에서 실행한 결과이므로 \로 표시되며, macOS나 Linux에서는 data/logs/app.txt로 표시됩니다.
parent는 상위 폴더, stem은 확장자를 뺀 파일 이름, suffix는 확장자입니다.
문자열을 +로 이어 붙여 경로를 만들면 구분자를 빠뜨리거나 운영체제마다 구분자가 달라 오류가 생기기 쉬우므로, 경로는 Path 객체로 다루는 것을 권장합니다.
정리
| 이름 | 설명 | 반환값/특징 |
|---|---|---|
| open(경로, 모드, encoding=인코딩) | 파일을 열어 파일 객체 생성 | 텍스트 모드는 TextIOWrapper |
| 파일.close() | 파일을 닫음 | 버퍼의 내용을 기록 |
| with open(…) as 파일: | 블록을 벗어나면 자동으로 닫음 | 오류가 발생해도 닫힘 |
| 파일.read(n) | 커서부터 n글자 (생략 시 끝까지) 읽기 | 문자열, 끝이면 '' |
| 파일.readline() | 커서부터 한 줄 읽기 | \n 포함 문자열 |
| 파일.readlines() | 남은 모든 줄 읽기 | 문자열 목록 |
| for 줄 in 파일: | 한 줄씩 반복 | 메모리 효율적 |
| 파일.write(문자열) | 커서 위치에 쓰기 | 쓴 문자 수 |
| 파일.writelines(목록) | 문자열 목록 쓰기 | 개행 자동 추가 없음 |
| 파일.tell() / seek(위치) | 커서 위치 확인 / 이동 | 텍스트 모드는 0이나 tell() 값으로 이동 |
| 파일.flush() | 버퍼의 내용을 즉시 기록 | 파일은 열린 상태 유지 |
| Path.read_text() / write_text() | 파일 전체 읽기 / 쓰기 | 열고 닫기를 자동 수행 |
- 파일은 항상
with문으로 열어 닫기를 보장합니다. 닫아야 버퍼의 내용이 기록되고 운영체제 자원이 반환됩니다. - 텍스트 파일은
encoding="utf-8"처럼 인코딩을 명시합니다. - 파일 객체는 커서 위치를 기억하는 한 방향 반복자이므로, 다시 읽으려면
seek(0)을 사용하거나 결과를 변수에 저장합니다.
댓글 남기기