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

Python 개선 제안 한국어 번역

PEP 534 – 누락된 표준 라이브러리 모듈에 대한 오류 개선

Author:
Tomáš Orsava <tomas.n at orsava.cz>, Petr Viktorin <encukou at gmail.com>, Alyssa Coghlan <ncoghlan at gmail.com>
Status:
Withdrawn
Type:
Standards Track
Created:
05-Sep-2016
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

Python은 전체 표준 라이브러리 없이 빌드되거나 배포되는 경우가 많습니다. 그러나 이처럼 누락된 표준 라이브러리 모듈을 임포트하지 못했을 때 이를 사용자에게 제대로 알려 주는 표준적이고 사용자 친화적인 방법은 아직 없습니다.

이 PEP는 존재할 것으로 예상되는 표준 라이브러리 모듈을 식별하고, 표준 라이브러리 모듈 임포트가 실패할 때 사용자에게 더 유익한 오류 메시지를 제공하는 메커니즘을 제안합니다.

PEP 철회

핵심 아이디어가 그동안 구현되었으므로 저자들은 이 PEP를 철회했습니다. 관련 기능으로는 표준 라이브러리 모듈을 나열하는 sys.stdlib_module_names API, 배포자가 사용자 정의 오류 메시지를 제공할 수 있게 하는 --with-missing-stdlib-config configure 옵션, 그리고 누락된 standard library 모듈에 대해 개선된 ModuleNotFoundError 오류 메시지가 있습니다. 예를 들면 다음과 같습니다:

>>> import zlib
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    import zlib
ModuleNotFoundError: Standard library module 'zlib' was not found

동기

Python 표준 라이브러리의 일부만 포함하는 데에는 여러 사용 사례가 있습니다. 그러나 표준 라이브러리 모듈이 왜 없는지, 그리고 이 상황을 어떻게 적절히 해결할 수 있는지를 사용자에게 알려 주는 사용자 친화적인 메커니즘은 아직 없습니다.

CPython

CPython 빌드 중에 의존성(예: SQLite 헤더 파일)이 없어서 파이썬 표준 라이브러리 모듈 중 하나(예: _sqlite3)를 컴파일할 수 없으면, 해당 모듈은 그냥 건너뜁니다. 이렇게 컴파일된 Python을 설치한 뒤 누락된 모듈 중 하나를 임포트하려고 하면, Python은 ModuleNotFoundError를 일으키며 실패합니다.

예를 들어, 로컬 시스템에서 sqlite-devel을 일부러 제거한 후에는 다음과 같습니다:

$ ./python -c "import sqlite3"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/ncoghlan/devel/cpython/Lib/sqlite3/__init__.py", line 23, in <module>
    from sqlite3.dbapi2 import *
  File "/home/ncoghlan/devel/cpython/Lib/sqlite3/dbapi2.py", line 27, in <module>
    from _sqlite3 import *
ModuleNotFoundError: No module named '_sqlite3'

이는 깔끔하게 빌드된 Python에 왜 표준 라이브러리 모듈이 없는지 이해하지 못하는 사용자를 혼란스럽게 할 수 있습니다.

Linux 및 기타 배포판

많은 Linux 배포판과 기타 배포판은 이미 표준 라이브러리의 일부를 독립된 패키지로 분리하고 있습니다. 가장 흔히 제외되는 모듈로는 그래픽 환경에 대한 의존성을 끌어들이는 tkinter 모듈, tkinter에 의존하는 idlelib(그리고 대부분의 Linux 데스크톱 환경은 자체 기본 코드 편집기를 제공합니다), 그리고 Python을 내부적으로 테스트하는 용도로만 쓰이면서도 크기가 나머지 표준 라이브러리 전체를 합친 것과 비슷한 test 패키지가 있습니다.

이러한 모듈을 제외하는 방법은 배포판마다 다릅니다. 예를 들어, 데비안은 Lib/tkinter/__init__.py 파일을 패치하여 import _tkinter 줄을 try-except 블록으로 감싸고, ImportError가 발생하면 오류 메시지에 다음 내용을 덧붙이기만 합니다: please install the python3-tk package [1]. Fedora와 기타 배포판은 생략된 모듈을 아예 포함하지 않으므로, 사용자는 그 모듈을 어디서 찾아야 할지 몰라 당황할 수 있습니다.

Fedora 29의 예시:

$ python3 -c "import tkinter"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'tkinter'

명세

존재할 것으로 예상되는 표준 라이브러리 모듈을 나열하는 API

어떤 모듈 이름을 표준 라이브러리에서 찾을 수 있을 것으로 예상되는지 더 쉽게 식별할 수 있도록, sysconfig 모듈에 다음 두 함수를 추가합니다:

  • sysconfig.get_stdlib_modules(): 모든 최상위 Python 표준 라이브러리 모듈(비공개 모듈 포함)의 이름 목록을 제공합니다
  • sysconfig.get_optional_modules()는 선택적인 공개 최상위 표준 라이브러리 모듈 이름을 나열합니다

sysconfig.get_optional_modules()의 결과와 기존의 sys.builtin_module_names는 모두 새로운 sysconfig.get_stdlib_modules() 함수가 제공하는 전체 목록의 부분집합이 됩니다.

이렇게 추가된 목록은 Python 빌드 과정에서 생성되며, 다른 sysconfig 값과 함께 _sysconfigdata-*.py 파일에 저장됩니다.

모듈이 “선택적” 목록에 포함될 수 있는 이유는 다음과 같습니다:

  • 모듈이 선택적 빌드 의존성에 의존하는 경우(예: _sqlite3, tkinter, idlelib)
  • 다른 이유로 비공개인 모듈이어서 모든 구현에 존재하지 않을 수 있는 경우 (예: _freeze_importlib, _collections_abc)
  • 플랫폼에 특화된 모듈이어서 모든 설치본에 존재하지 않을 수 있는 경우 (예: winreg)
  • test 패키지도 Python 런타임 설치본에서 자유롭게 생략할 수 있습니다. 이 패키지는 Python 프로젝트가 사용하는 런타임 라이브러리가 아니라 Python 구현을 테스트하는 용도이기 때문입니다 (테스트 유틸리티를 제공하는 공개 API는 unittest입니다)

(참고: 현재 모든 재배포자가 이 관행을 따르지는 않지만, 이 PEP는 ensurepip, venv, distutils 모듈을 모두 필수 모듈로 간주합니다)

기본 sys.excepthook 구현 변경

그런 다음 sys.excepthook 함수의 기본 구현을 수정합니다. 새 sysconfig 함수 두 개 중 하나가 Python 표준 라이브러리에 속한다고 식별하는 모듈을 임포트하지 못한 것을 감지하면, 이 구현은 적절한 메시지를 출력합니다.

선택적 빌드 의존성에 의존하거나 그 밖의 이유로 Python 설치 시 선택 사항으로 간주되는 모듈에 대한 수정된 오류 메시지:

$ ./python -c "import sqlite3"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/ncoghlan/devel/cpython/Lib/sqlite3/__init__.py", line 23, in <module>
    from sqlite3.dbapi2 import *
  File "/home/ncoghlan/devel/cpython/Lib/sqlite3/dbapi2.py", line 27, in <module>
    from _sqlite3 import *
ModuleNotFoundError: Optional standard library module '_sqlite3' was not found

선택적 최상위 패키지 전체가 없을 때 그 하위 모듈에 대한 수정된 오류 메시지:

$ ./python -c "import test.regrtest"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: Optional standard library module 'test' was not found

선택적 최상위 패키지는 있을 때 그 하위 모듈에 대한 수정된 오류 메시지:

$ ./python -c "import test.regrtest"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No submodule named 'test.regrtest' in optional standard library module 'test'

항상 사용할 수 있어야 하는 모듈에 대한 수정된 오류 메시지:

$ ./python -c "import ensurepip"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: Standard library module 'ensurepip' was not found

최상위 패키지는 있지만 표준 라이브러리 패키지의 하위 모듈이 없을 때의 수정된 오류 메시지:

$ ./python -c "import encodings.mbcs"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No submodule named 'encodings.mbcs' in standard library module 'encodings'

이렇게 수정한 오류 메시지는 누락된 모듈이 현재 환경에 없는 서드파티 의존성을 뜻하는 것이 아님을 분명히 합니다. 그 모듈은 원래 표준 라이브러리에서 제공되어야 하지만 어떤 이유로 사용할 수 없다는 뜻입니다.

설계 논의

sys.excepthook 수정

발생한 예외가 잡히지 않은 채로 프로그램이 종료되려 하거나 (대화형 세션에서) 제어가 프롬프트로 돌아갈 때 sys.excepthook 함수가 호출됩니다. 따라서 사용자 정의 오류 메시지를 넣기에 가장 알맞은 곳입니다. 잡힌 오류에는 영향을 주지 않으므로 Python 스크립트의 일반적인 실행도 느려지지 않습니다.

예상되는 표준 라이브러리 모듈 이름을 조회하는 공개 API

sysconfig.get_stdlib_modules()와 sysconfig.get_optional_modules() 함수를 추가하면, 오랫동안 요구되어 온 Python 표준 라이브러리 모듈 이름을 쉽게 나열하는 방법이 생깁니다 [2]. 이 함수들이 주는 여러 이점 가운데 하나로, 코드 분석, 프로파일링, 오류 보고 도구가 런타임 --ignore-stdlib 플래그를 더 쉽게 제공할 수 있습니다.

최상위 모듈 이름만 포함

이 PEP는 새 조회 API가 최상위 모듈 이름과 패키지 이름만 보고하도록 제안합니다. 제안한 오류 메시지를 만드는 데는 이 정보로 충분합니다. 또한 필요한 항목 수가 10분의 1 수준으로 줄고, 빌드 과정에서 관련 메타데이터를 생성하는 절차도 단순해집니다.

나중에 이 방식이 지나치게 제한적이라고 판단되면, 조회 API에 새 include_submodules 플래그를 추가할 수 있습니다. 그러나 이는 초기 제안에 포함되지 않습니다. 현재로서는 그렇게 해서 얻는 이점이 추가되는 복잡성을 정당화할 만큼 크지 않다고 보기 때문입니다.

이 제한에는 알려진 결과가 하나 있습니다. 새 기본 excepthook 구현은 잘못된 하위 모듈 이름도 실제로 없는 표준 라이브러리 하위 모듈과 같은 방식으로 보고합니다:

$ ./python -c "import unittest.muck"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No submodule named 'unittest.muck' in standard library module 'unittest'

비공개 최상위 모듈 이름을 선택적 표준 라이브러리 모듈로 나열하기

선택적 외부 빌드 의존성이 있는 모듈 중 상당수는 하이브리드 모듈로 작성되어 있습니다. 이런 모듈에서는 공용 Python 래퍼가 하부 외부 라이브러리에 대한 구현 의존적인 인터페이스를 감쌉니다. 비공개 최상위 모듈이 단순히 CPython의 구현 세부 사항이어서 다른 구현에서는 그 모듈을 아예 제공하지 않는 경우도 있습니다.

이런 모듈과 관련된 임포트 오류를 적절하게 보고하려면, 새 기본 excepthook 구현이 참조할 수 있도록 새 조회 API가 이 모듈들도 보고해야 합니다.

패키징 관련 모듈을 필수로 간주하기

일부 재배포자는 Python 전용 패키징 관련 모듈(distutils, ensurepip, venv)을 기본으로 설치하는 것을 그다지 반기지 않습니다. 개발자가 대신 해당 플랫폼 전용 도구를 쓰기를 바라기 때문입니다.

이 방식은 크로스 플랫폼 프로젝트를 개발하는 사람과 플랫폼에 독립적인 설치 안내서를 쓰려는 교육자에게 상호 운용성 문제를 일으킵니다. 그래서 이 PEP는 이 모듈들을 필수로 간주하고 선택적 모듈 목록에서 제외해야 한다고 봅니다.

보류된 아이디어

이 절에서 다루는 아이디어는 이 PEP가 실현에 도움을 줄 수 있는 개념이지만, 초기 제안의 범위에서는 제외합니다.

플랫폼 종속 모듈

일부 표준 라이브러리 모듈은 특정 플랫폼에서만 제공되므로 없을 수 있습니다. 예를 들어 winreg 모듈은 Windows에서만 사용할 수 있습니다:

$ python3 -c "import winreg"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'winreg'

현재 제안에서는 플랫폼 의존성 정보를 더 구조화된 방식으로 노출하려 하지 않고, 이러한 플랫폼 의존적인 모듈을 다른 모든 선택적 모듈과 함께 단순히 포함합니다.

그러나 the documentation을 위해 플랫폼 종속성을 적어도 “Windows”, “Unix”, “Linux”, “FreeBSD” 수준에서는 추적하고 있으므로, 이 정보를 프로그램에서 사용할 수 있게 노출하는 것도 가능해 보입니다.

__main__이 표준 라이브러리 모듈을 가릴 때 경고 출력하기

새로운 조회 API를 활용하면 새 기본 excepthook 구현이 __main__.__file__ 또는 __main__.__spec__.name이 표준 라이브러리 모듈과 일치하는 경우를 감지하여 적절한 경고를 출력할 수도 있습니다.

그러나 이런 작업을 지금 당장 반영할 필요는 없습니다. 실제로 진행하려면 사용자가 이 문제를 겪는 사례를 먼저 더 많이 검토해야 합니다. 또한 상황을 디버깅하는 데 도움이 될 정보를 더 제공하는 여러 방안도 살펴봐야 합니다.

다운스트림 배포자를 위한 권고 사항

Python 배포자는 site.py [*]를 패치하여 sys.excepthook 함수를 직접 구현함으로써 처리되지 않은 모든 예외에 대해 맞춤형 오류 메시지를 표시할 수 있습니다. 예를 들어 ModuleNotFoundError가 발생하면, 누락된 표준 라이브러리 모듈을 해당 배포판에 맞는 올바른 방법으로 설치하도록 사용자에게 안내할 수 있습니다.

일부 다운스트림 배포자는 이미 sys.excepthook을 패치하는 이 방법으로 플랫폼의 크래시 보고 메커니즘과 통합하고 있습니다.

하위 호환성

하위 호환성 문제는 없을 것으로 예상합니다. 누락된 의존성을 자체 방식으로 처리하려고 이미 Python 모듈을 패치하고 있는 배포판은 아무런 지장 없이 계속 그렇게 할 수 있습니다.

참조 구현 및 예제 구현

미정. 세부 사항은 CPython 빌드 시스템의 기능으로 실제로 무엇을 할 수 있는지에 따라 달라집니다(그러면 다른 구현은 이 데이터를 직접 다시 생성하지 않고 CPython에서 생성한 데이터를 사용할 수 있어야 합니다).

참고 사항 및 참고 문헌

이 PEP로 이어진 아이디어는 python-dev mailing list에서 논의되었고, 이후 python-ideas에서도 논의되었습니다.