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 + striplist(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 표준 동작 기준으로 동일하거나 유사한 결과를 냅니다. 개인 판단: “지금이라면 리스트 컴프리헨션을 먼저 쓸 것 같다”는 작성자의 회고입니다.


파일 한 줄씩 다루는 일은 자동화 스크립트의 출발점이 되는 경우가 많습니다. 이 작은 개행 손질 하나가 이후 파싱이나 문자열 비교 로직 전체를 깔끔하게 만들어주는 경우가 많아서, 한 번 제대로 정리해두면 오래 갑니다.

Similar Posts