PEP 597 – 선택적 EncodingWarning 추가
- Author:
- Inada Naoki <songofacandy at gmail.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 05-Jun-2019
- Python-Version:
- 3.10
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
새로운 경고 범주 EncodingWarning을 추가합니다. 이 경고는 open()의 encoding 인자가 생략되어 로케일별 기본 인코딩이 사용될 때 발생합니다.
이 경고는 기본적으로 비활성화되어 있습니다. 새로운 -X warn_default_encoding 명령줄 옵션과 새로운 PYTHONWARNDEFAULTENCODING 환경 변수를 사용하여 이 경고를 활성화할 수 있습니다.
encoding에 대한 "locale" 인자 값도 추가합니다. 이 값은 로케일 인코딩을 사용해야 한다는 것을 명시적으로 지정하여 경고가 발생하지 않게 합니다.
동기
기본 인코딩을 사용하는 것은 흔한 실수입니다
macOS나 Linux를 사용하는 개발자는 기본 인코딩이 항상 UTF-8인 것은 아니라는 사실을 잊을 수 있습니다.
예를 들어, setup.py에서 long_description = open("README.md").read()를 사용하는 것은 흔한 실수입니다. UTF-8로 인코딩된 README.md 파일에 ASCII가 아닌 문자(예: 이모지, 저자 이름, 저작권 기호 등)가 하나라도 있으면, 많은 Windows 사용자가 그러한 패키지를 설치할 수 없습니다.
PyPI에서 가장 많이 다운로드된 4000개 패키지 중 489개가 README에 ASCII가 아닌 문자를 사용하며, 82개는 ASCII가 아닌 문자가 포함된 파일에 인코딩을 지정하지 않아 UTF-8이 아닌 로케일에서 소스로부터 설치하는 데 실패합니다. [1]
또 다른 예는 logging.basicConfig(filename="log.txt")입니다. 일부 사용자는 이것이 기본적으로 UTF-8을 사용할 것이라고 기대할 수 있지만, 실제로 사용되는 것은 로케일 인코딩입니다. [2]
Python 전문가조차도 기본 인코딩이 UTF-8이라고 가정할 수 있습니다. 이로 인해 Windows에서만 발생하는 버그가 생깁니다. 예시는 [3], [4], [5], [6]을 참조하십시오.
encoding 인자가 생략되었을 때 경고를 발생시키면 이러한 실수를 찾는 데 도움이 됩니다.
로케일별 인코딩을 사용하는 명시적인 방법
open(filename)은 어떤 인코딩을 예상하는지 명시적으로 드러내지 않습니다:
- ASCII를 가정했다면 이는 버그가 아니지만, Windows에서, 특히 Latin-1이 아닌 로케일 인코딩에서 성능 저하를 일으킬 수 있습니다
- UTF-8을 가정했다면 이는 버그이거나 플랫폼 특화 스크립트일 수 있습니다
- 로케일 인코딩을 가정했다면 동작은 예상대로입니다(그러나 향후 Python 버전에서 기본값을 변경하면 달라질 수 있습니다)
이러한 관점에서 open(filename)은 가독성 있는 코드가 아닙니다.
encoding=locale.getpreferredencoding(False)를 사용하여 로케일 인코딩을 명시적으로 지정할 수 있지만, 이는 너무 길고 잘못 사용하기 쉽습니다(예: 인자로 False를 전달하는 것을 잊을 수 있습니다).
이 PEP는 로케일 인코딩을 지정하는 명시적인 방법을 제공합니다.
기본 인코딩을 UTF-8로 변경하기 위한 준비
UTF-8이 사실상의 표준 텍스트 인코딩이 되었으므로, 향후에는 파일을 열 때 UTF-8을 기본값으로 사용할 수도 있습니다.
그러나 이러한 변경은 많은 애플리케이션과 라이브러리에 영향을 미칩니다. encoding 인자가 생략된 모든 곳에서 DeprecationWarning을 발생시키기 시작한다면, 경고가 지나치게 많아 성가시고 고통스러울 것입니다.
이 PEP는 기본 인코딩의 변경을 제안하지는 않지만, 다음과 같은 방법으로 그러한 변경을 가능하게 하는 데 도움이 됩니다:
- 기본적으로
DeprecationWarning을 발생시키기 시작하기 전에 라이브러리에서encoding인자를 생략하는 경우를 줄입니다. - 사용자가
encoding="locale"을 전달하여 현재 경고와 앞으로 추가될 모든DeprecationWarning을 억제할 수 있게 하고, 이후 Python 버전에서 기본값이 바뀌더라도 일관된 동작을 유지하여 Python 3.10 이상의 모든 버전을 지원할 수 있게 합니다.
명세
EncodingWarning
Warning의 서브클래스로 새 경고 클래스 EncodingWarning을 추가합니다. 이 경고는 encoding 인자가 생략되어 로케일별 기본 인코딩이 사용될 때 발생합니다.
경고를 활성화하는 옵션
-X warn_default_encoding 옵션과 PYTHONWARNDEFAULTENCODING 환경 변수가 추가됩니다. 이 옵션과 환경 변수는 EncodingWarning을 활성화하는 데 사용됩니다.
sys.flags.warn_default_encoding도 추가됩니다. 이 플래그는 EncodingWarning이 활성화되었을 때 참입니다.
이 플래그가 설정되면 io.TextIOWrapper(), open(), 그리고 이들을 사용하는 다른 모듈은 encoding 인자가 생략되었을 때 EncodingWarning을 발생시킵니다.
EncodingWarning은 Warning의 서브클래스이므로 DeprecationWarning과 달리 기본적으로 표시됩니다(warn_default_encoding 플래그가 설정된 경우).
encoding="locale"
io.TextIOWrapper는 "locale"을 encoding의 유효한 인자로 받아들입니다. 의미는 현재의 encoding=None과 같습니다. 다만 encoding="locale"을 지정하면 io.TextIOWrapper가 EncodingWarning을 발생시키지 않는다는 점이 다릅니다.
io.text_encoding()
io.text_encoding()은 encoding=None 매개변수를 받아 io.TextIOWrapper() 또는 open()에 전달하는 함수를 위한 헬퍼입니다.
순수 Python 구현은 다음과 같습니다:
def text_encoding(encoding, stacklevel=1):
"""A helper function to choose the text encoding.
When *encoding* is not None, just return it.
Otherwise, return the default text encoding (i.e. "locale").
This function emits an EncodingWarning if *encoding* is None and
sys.flags.warn_default_encoding is true.
This function can be used in APIs with an encoding=None parameter
that pass it to TextIOWrapper or open.
However, please consider using encoding="utf-8" for new APIs.
"""
if encoding is None:
if sys.flags.warn_default_encoding:
import warnings
warnings.warn(
"'encoding' argument not specified.",
EncodingWarning, stacklevel + 2)
encoding = "locale"
return encoding
예를 들어 pathlib.Path.read_text()는 다음과 같이 이 함수를 사용할 수 있습니다:
def read_text(self, encoding=None, errors=None):
encoding = io.text_encoding(encoding)
with self.open(mode='r', encoding=encoding, errors=errors) as f:
return f.read()
io.text_encoding()을 사용하면 EncodingWarning은 read_text() 자체가 아니라 read_text()의 호출자에 대해 발생합니다.
영향을 받는 표준 라이브러리 모듈
이 변경은 많은 표준 라이브러리 모듈에 영향을 줍니다.
encoding=None을 받는 대부분의 API는 앞 절에서 설명한 대로 io.text_encoding()을 사용합니다.
로케일 인코딩을 기본 인코딩으로 사용하는 것이 합리적인 경우에는 대신 encoding="locale"을 사용합니다. 예를 들어 subprocess 모듈은 파이프의 기본 인코딩으로 로케일 인코딩을 사용합니다.
많은 테스트가 ASCII 텍스트 파일을 읽을 때 encoding을 지정하지 않고 open()을 사용합니다. 이러한 테스트는 encoding="ascii"를 사용하도록 다시 작성해야 합니다.
근거
선택적으로 활성화하는 경고
DeprecationWarning은 기본적으로 억제되지만, encoding 인자가 생략될 때마다 항상 DeprecationWarning을 발생시키면 경고가 지나치게 많아집니다.
경고가 지나치게 많으면 개발자가 DeprecationWarning을 무시하게 될 수 있습니다.
“locale”(로케일)은 코덱 별칭이 아닙니다
로케일은 런타임에 변경될 수 있으므로 “locale”을 코덱 별칭으로 추가하지 않습니다.
또한 TextIOWrapper는 encoding=None일 때 os.device_encoding()을 확인합니다. 이 동작은 코덱으로 구현할 수 없습니다.
하위 호환성
새 경고는 기본적으로 발생하지 않으므로 이 PEP는 100% 하위 호환됩니다.
상위 호환성
encoding에 "locale"을 인자로 전달하는 것은 상위 호환되지 않습니다. 이를 사용하는 코드는 3.10 미만의 Python에서 동작하지 않으며, 대신 LookupError: unknown encoding: locale을 발생시킵니다.
개발자들이 Python 3.9 지원을 중단할 수 있을 때까지 EncodingWarning은 누락된 encoding="utf-8" 인자를 찾는 용도로만 사용할 수 있습니다.
교육 방법
새로운 사용자를 위해
EncodingWarning은 크로스 플랫폼 코드를 작성하는 데 사용되므로 새로운 사용자에게 가르칠 필요는 없습니다.
텍스트 파일에는 UTF-8을 사용하고, 파일을 열 때는 encoding="utf-8"을 사용하도록 권장하기만 하면 됩니다.
숙련된 사용자를 위해
UTF-8로 인코딩된 텍스트 파일을 읽기 위해 open(filename)을 사용하는 것은 흔한 실수입니다. Windows에서는 UTF-8이 기본 인코딩이 아니므로 동작하지 않을 수 있습니다.
이러한 유형의 실수를 찾으려면 -X warn_default_encoding 또는 PYTHONWARNDEFAULTENCODING=1을 사용할 수 있습니다.
로케일 인코딩으로 인코딩된 텍스트 파일을 열 때 encoding 인자를 생략하는 것은 버그가 아니지만, Python 3.10 이상에서는 더 명시적이므로 encoding="locale"을 권장합니다.
참조 구현
논의
최신 논의 스레드는 다음과 같습니다: https://mail.python.org/archives/list/python-dev@python.org/thread/SFYUP2TWD5JZ5KDLVSTZ44GWKVY4YNCV/
- 왜 린터에서 구현하지 않습니까?
encoding="locale"과io.text_encoding()은 Python으로 구현되어야 합니다.open()이나TextIOWrapper()를 감싸는 함수의 호출자를 모두 찾기는 어렵습니다(io.text_encoding()절을 참조하십시오).
- 많은 개발자는 이 옵션을 사용하지 않을 것입니다.
참고 문헌
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.