깨진 건 데이터가 아니라 출력이었다 — python -I와 stdout 인코딩, 그리고 내릴 뻔한 잘못된 결론

베리체·7일 전
post-thumbnail

공공기관이 올린 134쪽짜리 PDF에서 품목 이름 몇 개가 나오는지 세고 있었다. 받은 파일은 남이 만든 문서라, 평소 습관대로 파이썬을 격리 모드로 돌렸다. -I를 붙이면 현재 폴더의 모듈을 import 경로에서 빼고 PYTHON* 환경변수도 무시한다. 낯선 파일 옆에서 스크립트를 돌릴 때 쓰는 안전장치다.

결과는 이렇게 나왔다.

�Ȱ� 0
��ǻ�� 3
���� 10

숫자보다 글자가 먼저 눈에 들어왔다. 검색어가 깨졌다. 깨진 검색어로 찾았으니 0이 나온 것도 당연하다. 그렇게 결론을 내리고 다음 단계로 넘어갔다.

1. 그럴듯한 설명

-I는 PYTHONIOENCODING을 무시한다. 나는 그 변수를 utf-8로 걸어 두고 있었다. 그러니 환경변수가 꺼지면서 무언가 인코딩이 꼬였고, heredoc으로 넘긴 스크립트 속 한글 문자열이 깨졌을 것이다. 앞뒤가 맞는 이야기였고, 실제로 -X utf8을 붙여 다시 돌리니 글자가 멀쩡하게 나왔다.

안경 0
컴퓨터 3
가구 10

여기서 멈췄으면 "-I가 한글 검색어를 깨뜨린다"는 메모가 남았을 것이다.

2. 숫자를 다시 보니

나중에 기록을 정리하다가 두 결과를 나란히 놓았다. 숫자가 똑같았다. 0, 3, 10. 검색어가 정말 깨졌다면 '컴퓨터'와 '가구'도 0이어야 한다. 깨진 문자열로 3건, 10건을 찾을 수는 없다.

그래서 문자열 자체를 찍어 봤다.

import sys
k = '안경'
print(sys.stdout.encoding, k.encode('utf-8').hex(), len(k))
# python -I
cp949 ec9588eab2bd 2
# python -X utf8 -I
utf-8 ec9588eab2bd 2

문자열은 두 경우 모두 같은 여섯 바이트, 두 글자였다. 달라진 건 sys.stdout.encoding 하나다. -I로 환경변수가 꺼지자 표준출력이 윈도우 기본 코드페이지(cp949)로 돌아갔고, UTF-8을 기대하는 터미널이 그 바이트를 엉뚱하게 그렸다. 데이터는 처음부터 멀쩡했다. 깨진 건 화면이었다.

3. 무엇이 위험했나

이번엔 결론이 같아서 다행이었다. 그 PDF 본문에는 정말로 '안경'이라는 단어가 없었다. 가장 가까운 건 '광학제품'이라는 넓은 이름 하나였다. 하지만 "검색어가 깨졌으니 결과를 믿을 수 없다"는 잘못된 진단이 메모로 남았다면, 다음에 같은 출력을 볼 때 멀쩡한 결과를 버리거나, 엉뚱한 곳을 고치러 갔을 것이다.

이번 일로 바꾼 습관은 두 가지다.

  • 글자가 깨져 보이면 출력부터 의심한다. 데이터를 의심하기 전에 sys.stdout.encoding을 찍고, 문제의 문자열을 .encode('utf-8').hex()로 바이트째 확인한다.
  • 격리 모드가 필요하면 python -X utf8 -I로 돌린다. -X는 명령줄 옵션이라 -I가 꺼 버리는 환경변수와 달리 그대로 살아남는다.

4. 남은 생각

증상을 설명하는 이야기가 그럴듯하면 거기서 확인을 멈추기 쉽다. 이번에 진단을 바로잡아 준 건 새 도구가 아니라 이미 화면에 떠 있던 숫자 두 줄이었다. 결과가 같다는 사실은 처음부터 거기 있었다.

profile
공개 데이터로 온라인 거래 피해를 기록합니다. 크롤링·DNS·데이터 정리 이야기를 씁니다.

0개의 댓글