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를 가정한다면 버그는 아니지만, 특히 Latin-1이 아닌 로캘 인코딩을 사용할 때 Windows에서 성능 저하가 발생할 수 있습니다.
  • 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을 발생시킵니다.

EncodingWarningWarning의 서브클래스이므로 DeprecationWarning과 달리 기본적으로 표시됩니다(warn_default_encoding 플래그가 설정된 경우).

encoding="locale"

io.TextIOWrapper"locale"encoding에 유효한 인자로 허용합니다. 현재의 encoding=None과 같은 의미이지만, encoding="locale"을 지정하면 io.TextIOWrapperEncodingWarning을 발생시키지 않습니다.

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()을 사용하면 read_text()자체가 아니라 read_text()의 호출자에게 EncodingWarning이 발생합니다.

영향을 받는 표준 라이브러리 모듈

이 변경으로 많은 표준 라이브러리 모듈이 영향을 받습니다.

encoding=None 매개변수를 허용하는 대부분의 API는 이전 절에 작성된 대로 io.text_encoding()을 사용합니다.

로케일 인코딩을 기본 인코딩으로 사용하는 것이 합리적인 경우에는 encoding="locale"를 대신 사용합니다. 예를 들어 subprocess 모듈은 파이프의 기본 인코딩으로 로케일 인코딩을 사용합니다.

많은 테스트에서 ASCII 텍스트 파일을 읽기 위해 encoding을 지정하지 않은 채 open()을 사용합니다. 이러한 테스트는 encoding="ascii"를 사용하도록 다시 작성해야 합니다.

근거

선택적 활성화 경고

DeprecationWarning은 기본적으로 억제되지만, encoding 인자가 생략될 때마다 DeprecationWarning을 항상 발생시키면 경고가 너무 많아집니다.

경고가 너무 많으면 개발자가 DeprecationWarning을 무시하게 될 수 있습니다.

“locale”은 코덱 별칭이 아닙니다.

로캘은 런타임에 변경될 수 있으므로 “locale”을 코덱 별칭으로 추가하지 않습니다.

또한, TextIOWrapperencoding=None일 때 os.device_encoding()을 확인합니다. 이 동작은 코덱에서 구현할 수 없습니다.

하위 호환성

새 경고는 기본적으로 발생하지 않으므로 이 PEP는 100% 하위 호환됩니다.

상위 호환성

"locale"encoding에 대한 인자로 전달하는 것은 상위 호환되지 않습니다. 이를 사용하는 코드는 Python 3.10보다 오래된 버전에서 작동하지 않으며, 대신 LookupError: unknown encoding: locale를 발생시킵니다.

개발자가 Python 3.9 지원을 중단할 수 있을 때까지 EncodingWarning은 누락된 encoding="utf-8" 인자를 찾는 데에만 사용할 수 있습니다.

이것을 가르치는 방법

신규 사용자의 경우

EncodingWarning은 플랫폼 간 코드를 작성하는 데 사용되므로 신규 사용자에게 이를 가르칠 필요는 없습니다.

텍스트 파일에는 UTF-8을 사용하고 파일을 열 때 encoding="utf-8"를 사용하도록 권장하면 됩니다.

숙련된 사용자의 경우

UTF-8로 인코딩된 텍스트 파일을 읽기 위해 open(filename)을 사용하는 것은 흔한 실수입니다. UTF-8이 기본 인코딩이 아니므로 Windows에서는 작동하지 않을 수 있습니다.

-X warn_default_encoding 또는 PYTHONWARNDEFAULTENCODING=1을 사용하여 이러한 유형의 실수를 찾을 수 있습니다.

로캘 인코딩으로 인코딩된 텍스트 파일을 열 때 encoding인자를 생략하는 것은 버그가 아니지만, encoding="locale"를 사용하는 것이 더 명시적이므로 Python 3.10 이상에서는 이를 권장합니다.

참조 구현

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]를 찾았습니다. 이는 이 옵션을 사용하여 잠재적인 문제를 찾을 수 있음을 보여 줍니다.

참고 자료