Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 597 – 선택적 EncodingWarning 추가

Author:
Inada Naoki <songofacandy at gmail.com>
Status:
Final
Type:
Standards Track
Created:
05-Jun-2019
Python-Version:
3.10

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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://github.com/python/cpython/pull/19481

논의

최신 논의 스레드는 다음과 같습니다: https://mail.python.org/archives/list/python-dev@python.org/thread/SFYUP2TWD5JZ5KDLVSTZ44GWKVY4YNCV/

  • 왜 린터에서 구현하지 않습니까?
    • encoding="locale"과 io.text_encoding()은 Python으로 구현되어야 합니다.
    • open()이나 TextIOWrapper()를 감싸는 함수의 호출자를 모두 찾기는 어렵습니다(io.text_encoding() 절을 참조하십시오).
  • 많은 개발자는 이 옵션을 사용하지 않을 것입니다.
    • 일부 개발자는 이 옵션을 사용하여 자신이 사용하는 라이브러리에 경고를 보고할 것이므로, 많은 개발자가 이 옵션을 활성화하지 않더라도 이 옵션은 가치가 있습니다.
    • 예를 들어, 저는 참조 구현으로 pip install -U pip을 실행하여 [7]과 [8]을 발견했고, tox를 실행하여 [9]를 발견했습니다. 이는 이 옵션을 사용하여 잠재적인 문제를 찾을 수 있음을 보여 줍니다.

참고 문헌