📋 목차
파이썬으로 텍스트 파일을 읽고 쓸 때 open() 함수와 with 구문만 제대로 이해하면 대부분의 파일 처리 작업이 해결되는데, 처음 배울 때 인코딩 오류와 close() 누락 때문에 꽤 고생했던 경험을 정리해 봤어요.
저도 파이썬 처음 배울 때 파일 입출력이 제일 만만해 보였거든요. print()로 콘솔에 출력하는 건 잘 되니까, 파일에 쓰는 것도 금방이겠지 싶었죠. 근데 막상 해보니까 멀쩡하게 작성한 코드가 한글만 넣으면 깨지고, 분명 파일에 썼는데 열어보면 텅 비어 있고. 한 번은 close()를 안 써서 데이터가 증발한 적도 있었어요. 그때 “아, 이거 대충 넘어가면 안 되겠구나” 싶더라고요.
그래서 이 글에서는 제가 직접 겪었던 삽질 포인트를 중심으로, open() 함수의 모드별 차이부터 with 구문이 왜 필수인지, 그리고 인코딩 문제를 한 번에 잡는 방법까지 차근차근 풀어볼게요.
open() 함수, 생각보다 함정이 많더라고요
파이썬에서 파일을 다루려면 무조건 open()부터 시작해요. 문법 자체는 심플한데, 모드 하나 잘못 지정하면 기존 파일이 통째로 날아가버리거든요. 기본 형태는 이래요.
파일객체 = open(“파일이름”, “모드”, encoding=”인코딩”)
여기서 모드가 핵심이에요. ‘r’은 읽기, ‘w’는 쓰기, ‘a’는 추가. 딱 세 글자인데 이걸 헷갈려서 사고가 나요. 제가 처음에 저질렀던 실수가 뭐냐면, 기존 로그 파일에 내용을 추가하려고 ‘a’ 모드를 써야 하는데 습관적으로 ‘w’를 넣은 거예요. 실행하는 순간 3일치 로그가 증발했죠. ‘w’ 모드는 파일을 여는 그 순간 기존 내용을 전부 지워버리거든요. 파일이 없으면 새로 만들어주는 건 좋은데, 있는 파일을 열 때가 문제예요.
‘r’ 모드도 주의할 게 있어요. 파일이 존재하지 않으면 바로 FileNotFoundError가 터져요. 그래서 파일 존재 여부를 먼저 확인하거나, try-except로 감싸는 습관이 필요하더라고요. 반면에 ‘w’랑 ‘a’는 파일이 없으면 알아서 만들어줘요. 같은 open()인데 모드에 따라 동작이 이렇게 달라지는 게 처음엔 좀 혼란스러웠어요.
그리고 ‘x’ 모드라는 것도 있는데, 이건 파일이 이미 존재하면 에러를 내요. 실수로 덮어쓰는 걸 방지하고 싶을 때 쓰면 좋아요. 다만 자주 쓰이는 건 아니고, 저도 자동화 스크립트에서 백업 파일 생성할 때 한두 번 써본 정도예요.
파일 쓰기(w·a 모드)로 데이터 저장하기
파일에 뭔가를 쓰는 건 write() 메서드 하나로 끝나요. 근데 여기서도 미묘한 차이들이 있어서, 처음엔 “왜 줄바꿈이 안 되지?” 하고 한참 헤맸거든요.
write()는 문자열만 받아요. 숫자를 넣으면 TypeError가 나요. 그래서 str()로 변환하거나 f-string을 써야 하는데, 초보 때는 이걸 모르고 f.write(42) 이렇게 넣었다가 에러를 마주했던 기억이 나요. 그리고 write()는 줄바꿈을 자동으로 안 넣어줘요. print()랑 다른 점이에요. 줄을 바꾸고 싶으면 반드시 ‘\n’을 직접 넣어야 해요.
여러 줄을 한꺼번에 쓰고 싶을 때는 writelines()가 편하더라고요. 리스트를 통째로 넘기면 되거든요. 다만 이것도 줄바꿈을 자동으로 안 넣어줘요. lines = [“첫째 줄\n”, “둘째 줄\n”, “셋째 줄\n”] 이런 식으로 각 요소에 ‘\n’을 미리 붙여둬야 해요. 이름이 writelines라서 알아서 줄 구분해줄 것 같은데, 실제론 그냥 리스트 요소를 쭉 이어 쓸 뿐이에요.
⚠️ 주의
‘w’ 모드로 파일을 열면 기존 내용이 전부 삭제돼요. 이건 open()을 호출하는 순간 발생하는 거라, write()를 한 번도 안 해도 파일이 비워져요. 기존 데이터를 유지하면서 추가하려면 반드시 ‘a'(append) 모드를 쓰세요.
‘a’ 모드는 파일 끝에 커서를 놓고 시작해요. 그래서 로그 파일이나 일기장 같은 데이터를 쌓아가는 용도에 딱이에요. 저는 매일 실행되는 크롤링 스크립트의 결과를 ‘a’ 모드로 하나의 파일에 계속 추가하는 방식으로 쓰고 있는데, 한 달 넘게 문제없이 잘 동작하고 있어요.
read, readline, readlines — 읽기 함수 세 형제 비교
파일을 읽는 방법이 세 가지나 있어서 처음에 뭘 써야 할지 감이 안 왔어요. 결론부터 말하면, 상황에 따라 다르더라고요.
| 메서드 | 반환 타입 | 적합한 상황 |
|---|---|---|
| read() | 문자열 전체 | 파일 크기가 작을 때 |
| readline() | 한 줄 문자열 | 한 줄씩 처리할 때 |
| readlines() | 리스트 | 줄 단위 리스트 필요 시 |
read()는 파일 내용을 통째로 하나의 문자열로 가져와요. 가장 직관적이고 간단한데, 파일이 수백 MB라면 메모리가 펑 터질 수 있어요. 설정 파일이나 짧은 텍스트 파일에 쓰기 좋아요.
readline()은 호출할 때마다 한 줄씩 읽어요. 더 이상 읽을 게 없으면 빈 문자열(”)을 반환하는데, 이걸 이용해서 while 루프를 돌릴 수 있거든요. 근데 솔직히 이 방식은 좀 번거로워요. 차라리 파일 객체를 직접 for 문에 넣는 게 훨씬 깔끔해요. for line in f: 이렇게요. 내부적으로 readline()을 호출하는 것과 같은 효과인데, 코드가 압도적으로 간결해지거든요.
readlines()는 모든 줄을 읽어서 리스트로 돌려줘요. [“첫 줄\n”, “둘째 줄\n”, …] 이런 형태예요. 줄 끝에 ‘\n’이 붙어 있어서, 보통 strip()이랑 같이 쓰게 되더라고요. 저는 CSV 아닌 단순 목록 파일을 읽을 때 readlines() 쓰고 리스트 컴프리헨션으로 strip() 처리하는 패턴을 제일 많이 써요.
한 가지 의외였던 건, 대용량 파일을 다룰 때예요. 한 번은 2GB짜리 로그 파일을 read()로 열었다가 컴퓨터가 멈춘 적이 있어요. 그때부터 큰 파일은 무조건 for line in f: 패턴으로 한 줄씩 처리하게 됐어요. 메모리를 거의 안 잡아먹거든요.
with 구문을 꼭 써야 하는 이유
예전에는 파일을 열고 나서 반드시 f.close()를 호출해야 했어요. 근데 사람이 까먹잖아요. 특히 코드 중간에 에러가 터지면 close()가 실행이 안 돼요. 그러면 파일이 열린 채로 남아서 데이터가 제대로 저장 안 되거나, 다른 프로그램에서 그 파일을 못 여는 문제가 생기거든요.
with 구문은 이 문제를 완벽하게 해결해줘요. with open(“파일”, “모드”) as f: 블록 안에서 작업하면, 블록을 벗어나는 순간 파이썬이 알아서 파일을 닫아줘요. 에러가 나도요. 내부적으로는 컨텍스트 매니저(Context Manager)라는 메커니즘이 동작하는 건데, 지금 단계에서는 “with 쓰면 close() 안 써도 된다” 정도만 기억하면 충분해요.
💬 직접 써본 경험
예전에 try-finally로 close()를 보장하는 코드를 쓴 적이 있었는데, 동료가 “그냥 with 쓰면 되는데 왜 이렇게 하냐”고 해서 바꿨어요. 코드가 4줄에서 2줄로 줄더라고요. 그 뒤로 open()을 쓸 때는 무조건 with부터 치는 습관이 생겼어요.
그리고 with 블록 안에서 만든 변수는 블록 밖에서도 쓸 수 있어요. 이건 좀 의외인데, 파이썬에서 if, for, while, with 블록은 변수 스코프를 제한하지 않거든요. 다만 파일 객체 f는 with 블록이 끝나면 닫히니까, 블록 밖에서 f.read()를 시도하면 에러가 나요. 변수에 미리 담아두면 되긴 하는데, 이 부분 때문에 헷갈리는 사람이 은근 많더라고요.
f.closed 속성으로 파일이 닫혔는지 확인할 수도 있어요. with 블록 안에서는 False, 밖에서는 True가 나오거든요. 디버깅할 때 가끔 유용해요.
인코딩 지옥에서 살아남기 — UTF-8 습관 들이기
파이썬 파일 입출력에서 가장 많은 사람을 좌절시키는 게 인코딩 문제예요. 영어만 쓰면 괜찮은데, 한글이 들어가는 순간 UnicodeDecodeError가 뜨거든요. 저도 처음엔 이게 뭔 소린가 싶었어요.
핵심은 간단해요. open()에 encoding=”utf-8″을 넣으세요. 끝이에요. 사실 이 한 줄이면 한글 관련 문제의 90%는 해결돼요. 윈도우에서 파이썬의 기본 인코딩이 UTF-8이 아니라 CP949(EUC-KR 계열)인 게 원인이거든요. macOS나 리눅스는 기본이 UTF-8이라 문제가 덜 한데, 윈도우에서 개발하는 분들이 특히 많이 겪는 문제예요.
📊 실제 데이터
PEP 686에 따르면, 파이썬 3.15부터 UTF-8 모드가 기본값으로 변경돼요. 즉, 앞으로는 encoding=”utf-8″을 안 써도 UTF-8로 동작하게 되는 거예요. 하지만 현재 대부분의 프로젝트가 3.10~3.12 버전을 사용하고 있으니, 당분간은 명시하는 게 안전해요.
가끔 다른 프로그램이 만든 파일을 읽을 때 UTF-8이 아닌 경우도 있어요. 엑셀에서 CSV로 저장하면 CP949로 저장되는 경우가 꽤 있거든요. 이럴 때는 encoding=”cp949″로 바꿔보면 대부분 해결돼요. 그래도 안 되면 errors=”ignore”를 추가해서 일단 읽은 다음, 어떤 인코딩인지 chardet 같은 라이브러리로 확인해보는 방법도 있어요.
한 가지 흔한 오해가 있는데, “파이썬 3는 유니코드니까 인코딩 문제가 없다”는 생각이에요. 파이썬 3 내부에서 문자열은 유니코드로 처리하는 게 맞지만, 파일을 읽고 쓸 때는 바이트 변환이 필요하고, 이 변환 규칙이 바로 인코딩이에요. 그래서 파일 입출력에서는 여전히 인코딩을 신경 써야 하거든요.
실전에서 자주 쓰는 파일 입출력 패턴 모음
이론은 여기까지 하고, 실제로 제가 반복해서 쓰는 패턴들을 공유해볼게요.
첫 번째는 파일 전체 읽어서 변수에 담기예요. 설정 파일이나 템플릿 파일 읽을 때 이 패턴을 제일 많이 써요. with open(“config.txt”, “r”, encoding=”utf-8″) as f: data = f.read() 이게 끝이에요. 이후에 data를 가지고 split()이든 replace()든 마음대로 처리하면 돼요.
두 번째는 줄 단위로 리스트 만들기예요. with open(“list.txt”, “r”, encoding=”utf-8″) as f: lines = [line.strip() for line in f] 이 한 줄이면 깔끔한 리스트가 나와요. strip()으로 줄 끝 ‘\n’도 제거되고, 빈 줄도 공백 문자열로 처리돼요. 빈 줄까지 제거하고 싶으면 조건을 추가해서 [line.strip() for line in f if line.strip()] 이렇게 하면 되고요.
세 번째는 리스트를 파일로 저장하기예요. 데이터 수집 후 결과를 저장할 때 자주 쓰거든요. with open(“result.txt”, “w”, encoding=”utf-8″) as f: f.write(“\n”.join(result_list)) 이러면 리스트 요소가 줄바꿈으로 구분된 텍스트 파일이 돼요.
💡 꿀팁
파이썬 3.4 이상이라면 pathlib도 고려해보세요. Path(“파일.txt”).read_text(encoding=”utf-8″)으로 open-read-close를 한 줄에 끝낼 수 있어요. 단, append 모드는 지원하지 않아서 추가 쓰기가 필요하면 여전히 open()을 써야 해요.
네 번째는 파일 경로 다루기인데, 윈도우에서 역슬래시(\) 때문에 에러가 나는 경우가 정말 많아요. “C:\new\test.txt”라고 쓰면 \n이 줄바꿈으로, \t가 탭으로 해석돼버리거든요. 해결법은 세 가지예요. 슬래시를 쓰거나(“C:/new/test.txt”), 역슬래시를 두 번 쓰거나(“C:\\new\\test.txt”), 문자열 앞에 r을 붙이면(r”C:\new\test.txt”) 돼요. 저는 그냥 슬래시 쓰는 게 가장 편하더라고요.
다섯 번째로, 파일이 존재하는지 먼저 확인하고 싶을 때가 있잖아요. import os 한 다음 os.path.exists(“파일.txt”)로 True/False를 받을 수 있어요. pathlib을 쓴다면 Path(“파일.txt”).exists()도 같은 역할이고요. 파일 없을 때 ‘r’ 모드로 열면 에러가 나니까, 이 확인 과정을 습관화하면 좋아요.
자주 묻는 질문 (FAQ)
Q1. open()으로 파일을 열었는데 내용이 비어있어요. 왜 그런가요?
‘w’ 모드로 열면 기존 내용이 전부 삭제돼요. 읽기 목적이라면 ‘r’ 모드를 사용해야 하고, 기존 내용에 추가하려면 ‘a’ 모드를 써야 해요. 모드를 생략하면 기본값 ‘r’이 적용돼요.
Q2. with 구문 없이 그냥 open()만 써도 되나요?
동작은 하지만 권장하지 않아요. close()를 깜빡하거나 중간에 에러가 나면 파일이 닫히지 않아 데이터 손실이 생길 수 있거든요. with를 쓰면 자동으로 닫히니까 특별한 이유가 없다면 항상 with를 쓰세요.
Q3. read()와 readlines()를 같은 파일에서 연속으로 호출하면 어떻게 되나요?
read()를 먼저 호출하면 커서가 파일 끝으로 이동해서, 이후 readlines()는 빈 리스트를 반환해요. 다시 읽으려면 f.seek(0)으로 커서를 처음으로 되돌려야 해요.
Q4. 바이너리 파일(이미지, PDF 등)도 open()으로 열 수 있나요?
네, 모드에 ‘b’를 붙이면 돼요. ‘rb’는 바이너리 읽기, ‘wb’는 바이너리 쓰기예요. 이 경우 encoding 매개변수는 사용하지 않고, bytes 타입으로 데이터를 주고받아요.
Q5. 파일에 한글을 썼는데 메모장에서 열면 깨져요. 어떻게 하나요?
encoding=”utf-8″을 지정했는지 확인하세요. 윈도우 기본 메모장은 최신 버전에서 UTF-8을 잘 인식하지만, 구버전이라면 encoding=”utf-8-sig”를 쓰면 BOM(Byte Order Mark)이 추가되어 호환성이 좋아져요.
본 포스팅은 개인 경험과 공개 자료를 바탕으로 작성되었으며, 전문적인 의료·법률·재무 조언을 대체하지 않습니다. 정확한 정보는 해당 분야 전문가 또는 공식 기관에 확인하시기 바랍니다.
파이썬 파일 입출력은 open() 함수의 모드 선택, with 구문으로 안전하게 닫기, encoding=”utf-8″ 습관 — 이 세 가지만 확실히 잡으면 대부분의 상황을 커버할 수 있어요.
처음 시작하는 분이라면 일단 with open(“파일”, “r”, encoding=”utf-8″) as f: 패턴을 손에 익히세요. 여기서 모드만 바꾸면 읽기, 쓰기, 추가가 전부 되니까요. 이미 기본은 아는 분이라면 pathlib이나 대용량 파일 스트리밍 처리 쪽으로 한 단계 넓혀보시면 좋겠어요.
혹시 파일 입출력 관련해서 에러가 나거나 궁금한 점이 있으면 댓글로 남겨주세요. 비슷한 삽질을 해본 사람으로서 도움이 될 수 있을 거예요. 이 글이 도움이 됐다면 공유도 부탁드려요!