Python readlines 개행문자 제거 — strip vs rstrip vs 리스트 컴프리헨션
파일을 줄 단위로 읽으면 줄 끝에 \n이 붙어서 이후 처리할 때 거슬리죠. 이 글은 2021년 1월에 짧게 메모해뒀던 코드를 5년여 만에 다시 꺼내 정리한 보완본입니다. 원본은 map+lambda+strip 한 가지 패턴만 다뤘는데, 제목에 “3가지 방법”이라고 써놓고 본문은 하나뿐인 비대칭이 내내 마음에 걸렸습니다. 이번에 세 가지 패턴을 모두 코드와 함께 정리해봤습니다.
왜 개행문자가 따라붙는가
Python 3 표준 동작 기준으로, f.readlines()는 파일의 각 줄을 줄 끝 \n까지 포함한 문자열로 반환합니다.
예를 들어 abc.txt에 아래 내용이 있다고 가정하면:
hello
world
python
f.readlines()는 ["hello\n", "world\n", "python\n"]을 돌려줍니다. 줄 끝의 \n을 그대로 두면 문자열 비교나 출력에서 예상치 못한 공백이 끼어들기 때문에 한 단계 손질이 필요합니다.
그래서 아래 세 가지 방법이 등장합니다.
방법 ① map + lambda + strip — 원본 코드
원본(2021-01-11) 코드를 그대로 인용합니다.
f = open('abc.txt', 'r')
line = f.readlines()
line = list(map(lambda s: s.strip(), line))
참고: 이 코드는 2021년 원본을 그대로 살린 것으로
f.close()호출이 빠져 있습니다. 실제 스크립트에서 사용한다면f.close()를 명시적으로 호출하거나, 방법 ②·③처럼with블록으로 감싸는 것을 권장합니다.
이 한 줄에 네 가지 함수가 맞물려 돌아갑니다.
strip(): 문자열 앞뒤의 공백 문자(스페이스·탭·개행)를 모두 삭제하는 메서드입니다. 삭제할 문자를 따로 지정하지 않으면 모든 blank 문자를 삭제합니다.lambda s: s.strip():s를 받아s.strip()결과를 바로 반환하는 익명 함수입니다. 일반 함수와 달리return을 따로 쓸 필요 없이 표현식 값이 바로 반환되며, 함수를 인자로 받는 다른 함수를 호출할 때 인라인으로 간단히 씁니다.map(함수, iterable): iterable의 각 요소에 함수를 적용한 결과를 묶어 반환합니다. 여기서는line(파일의 각 줄 리스트)에 strip 람다를 적용합니다.list(iterable): map 결과(map 객체)를 리스트로 변환합니다. map만 쓰면 map 객체가 반환되므로, 리스트로 쓰려면list()로 감싸야 합니다.
가상 입력 ["hello\n", "world\n"] 기준, 결과는 ["hello", "world"]가 됩니다.
함수형 스타일에 익숙하면 한 줄로 깔끔하게 처리할 수 있지만, 코드 리뷰에서 한 번 더 눈길이 가는 형태이기도 합니다.
방법 ② 리스트 컴프리헨션 + strip
방법 ①은 open()을 직접 쓰는 원본 코드 그대로입니다. 방법 ②부터는 with open('abc.txt', 'r') as f: 형태의 컨텍스트 매니저를 사용합니다. with 블록을 빠져나올 때 파일이 자동으로 닫히기 때문에 close() 호출을 빠뜨리는 실수를 방지할 수 있습니다. 이 패턴은 Python 표준 권장 방식입니다.
with open('abc.txt', 'r') as f:
lines = [s.strip() for s in f.readlines()]
[s.strip() for s in f.readlines()]는 for 루프로 풀면 이렇게 됩니다.
lines = []
for s in f.readlines():
lines.append(s.strip())
두 코드는 동일하게 동작합니다. 리스트 컴프리헨션은 이 패턴을 한 줄로 줄인 Python 관용 문법입니다. 방법 ①과 의미상 동일한 결과를 내면서, lambda·map·list()를 중첩하지 않아도 되니 처음 읽는 사람도 “각 줄에서 strip한다”는 의도를 바로 파악할 수 있습니다.
가상 입력 ["hello\n", "world\n"] 기준, 결과는 ["hello", "world"]입니다.
방법 ③ rstrip(‘\n’) — 개행만 정확히
with open('abc.txt', 'r') as f:
lines = [s.rstrip('\n') for s in f.readlines()]
인자 없는 strip()은 앞뒤 모든 공백 문자를 지웁니다. 그런데 의도가 “줄 끝 개행만 제거”라면 rstrip('\n')이 더 의도와 일치합니다. Python 표준 동작 기준으로, rstrip('\n')은 문자열 오른쪽 끝의 \n만 제거하고 앞쪽 들여쓰기(스페이스·탭)는 건드리지 않습니다.
들여쓰기가 의미 있는 텍스트를 다룰 때 차이가 납니다. 코드 파일이나 들여쓰인 마크다운을 strip()으로 처리하면 앞쪽 공백까지 날아갈 수 있습니다.
가상 입력 [" hello\n", "world\n"] 기준으로 비교:
| 처리 방식 | 결과 |
|---|---|
s.strip() | ["hello", "world"] — 앞쪽 공백도 제거됨 |
s.rstrip('\n') | [" hello", "world"] — 앞쪽 공백 유지, 개행만 제거 |
“이게 가장 정확합니다”라기보다, 의도가 “개행만”이라면 rstrip('\n')이 그 의도를 더 정확하게 표현합니다.
어떤 걸 써야 하나
| 방법 | 코드 형태 | 어울리는 상황 |
|---|---|---|
| ① map + lambda + strip | list(map(lambda s: s.strip(), lines)) | 함수형 스타일 선호 — 코드 리뷰 시 재독 필요할 수 있음 |
| ② 리스트 컴프리헨션 + strip | [s.strip() for s in lines] | 가독성 우선, 가장 흔히 보이는 형태 |
| ③ rstrip(‘\n’) | [s.rstrip('\n') for s in lines] | 들여쓰기·앞쪽 공백을 보존하고 싶을 때 |
세 방법 모두 Python 표준 문법·동작 범위 안에서 동작하며, 외부 라이브러리 없이 바로 씁니다. 성능 수치보다는 코드를 함께 보는 사람과 맥락에 맞는 쪽을 고르는 게 현실적입니다.
5년 뒤 한마디
2021년에 이 코드를 처음 메모했을 때는 map+lambda로 한 줄에 처리하는 게 꽤 멋져 보였습니다. 지금 다시 짠다면 가독성 때문에 리스트 컴프리헨션 쪽을 먼저 꺼낼 것 같습니다. 어느 쪽이 절대적으로 더 낫다기보다는, 함께 코드를 보는 상황과 팀 컨벤션에 따라 달라지는 취향의 영역이라고 생각합니다.
팩트: 세 방법 모두 Python 표준 동작 기준으로 동일하거나 유사한 결과를 냅니다. 개인 판단: “지금이라면 리스트 컴프리헨션을 먼저 쓸 것 같다”는 작성자의 회고입니다.
파일 한 줄씩 다루는 일은 자동화 스크립트의 출발점이 되는 경우가 많습니다. 이 작은 개행 손질 하나가 이후 파싱이나 문자열 비교 로직 전체를 깔끔하게 만들어주는 경우가 많아서, 한 번 제대로 정리해두면 오래 갑니다.