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

Python 개선 제안 한국어 번역

PEP 499 – python -m foosys.modules'foo'도 바인딩해야 합니다.

Author:
Cameron Simpson <cs at cskk.id.au>, Chris Angelico <rosuav at gmail.com>, Joseph Jevnik <joejev at gmail.com>
BDFL-Delegate:
Alyssa Coghlan
Status:
Deferred
Type:
Standards Track
Created:
07-Aug-2015
Python-Version:
3.10

Table of Contents

번역·라이선스 안내

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

PEP 연기

이 PEP의 구현은 현재 2020년 4월 Python 3.9 기능 동결에 맞춰 준비될 것으로 예상되지 않으므로, Python 3.10으로 12개월 연기되었습니다.

초록

다음과 같이 Python 명령줄에서 모듈을 주 프로그램으로 사용할 때:

python -m module.name …

해당 모듈을 프로그램 내에서 다시 가져오면 모듈의 서로 독립적인 인스턴스 두 개가 실수로 생기기 쉽습니다. 이 PEP는 이 문제를 해결하는 방법을 제안합니다.

Python의 -m 옵션을 통해 모듈을 호출하면 해당 모듈은 sys.modules['__main__']에 바인딩되고 .__name__ 속성은 '__main__'으로 설정됩니다. 이를 통해 많은 모듈의 하단에 있는 다음과 같은 표준 “주 프로그램” 상용구 코드를 사용할 수 있습니다.:

if __name__ == '__main__':
    sys.exit(main(sys.argv))

그러나 위의 명령줄 호출을 사용하면 모듈이 실제로 공식 이름인 module.name으로 가져와졌다고 추론하는 것이 자연스럽고, 따라서 프로그램이 해당 이름을 다시 가져오면 동일한 모듈 인스턴스를 얻을 것이라고 생각하게 됩니다.

실제로는 모듈이 '__main__'으로만 가져와졌습니다. 다시 가져오면 별개의 모듈 인스턴스를 얻게 되며, 이로 인해 혼란스러운 버그가 발생할 수 있습니다. 이는 모두 모듈 전역 객체의 인스턴스가 각 모듈에 하나씩 두 개 존재하기 때문에 발생합니다.

예는 다음과 같습니다:

모듈 수준 데이터 구조
일부 모듈은 일반적으로 비공개인 캐시나 레지스트리와 같은 기능을 모듈 수준 전역 변수로 제공합니다. 모듈의 두 번째 인스턴스는 두 번째 데이터 구조를 생성합니다. 해당 구조가 re 모듈과 같은 캐시라면 캐시가 두 개 존재하여 메모리를 낭비하게 됩니다. 해당 구조가 값과 핸들러의 매핑과 같은 공유 레지스트리라면 한 레지스트리에 핸들러를 등록한 다음 다른 레지스트리를 통해 사용하려고 할 수 있으며, 이 경우 해당 핸들러를 찾을 수 없습니다.
센티널
모듈이 제공하는 센티널 값에 대한 표준 검사는 is를 사용하는 동일성 비교입니다. 이는 객체가 호환되지 않을 때 두 값을 “같음”으로 잘못 비교하거나(예를 들어 0과 비슷한 경우), TypeError를 발생시킬 수 있는 동등성 같은 신뢰할 수 없는 “비슷해 보이는” 비교를 피하기 때문입니다. 모듈 인스턴스가 두 개 있으면 센티널 인스턴스도 두 개 있으며, is를 통해 인식되는 것은 그중 하나뿐입니다.
클래스
모듈이 두 개 있으면 제공되는 모든 클래스의 클래스 정의가 중복됩니다. 이러한 클래스와 그 서브클래스를 인식하는 데 의존하는 모든 연산은 참조 클래스(두 모듈 중 하나에서 가져온 것)를 어디서 얻는지, 비교할 클래스나 인스턴스를 어디서 얻는지에 따라 실패하기 쉽습니다. 이는 isinstance, issubclasstry/except 구문에도 영향을 줍니다.

제안

이 상황을 해결하려면 -m 옵션이 구현되는 방식을 간단히 변경하기만 하면 된다고 제안합니다. 모듈 객체를 sys.modules['__main__']에 바인딩하는 것에 더해, sys.modules['module.name']에도 바인딩하는 것입니다.

Alyssa (Nick) Coghlan은 다음과 같이 runpy 모듈의 _run_module_as_main 함수를 수정하는 것만큼 간단하다고 제안했습니다.:

main_globals = sys.modules["__main__"].__dict__

대신 다음과 같이 하는 것입니다.:

main_module = sys.modules["__main__"]
sys.modules[mod_spec.name] = main_module
main_globals = main_module.__dict__

Joseph Jevnik은 패키지인 모듈이 이미 이 제안과 매우 유사한 작업을 수행한다고 지적했습니다. 즉, __init__.py 파일은 모듈의 정식 이름에 바인딩되고 __main__.py 파일은 “__main__”에 바인딩됩니다. 따라서 이중 임포트 문제가 발생하지 않습니다. 따라서 이 PEP는 단순한 비패키지 모듈에만 영향을 주도록 제안합니다.

고려 사항 및 사전 조건

모듈 피클링

Alyssa는 다음을 제안하는 issue 19702를 언급했습니다(이슈에서 인용함).

  • runpy는 __main__이 임포트 시스템을 통해 실행될 때 sys.modules에서 __spec__.name으로도 별칭이 지정되도록 보장합니다.
  • __main__.__spec__이 설정되어 있으면 pickle은 __main__에 정의된 클래스, 함수 및 메서드를 피클링할 때 __name__ 대신 __spec__.name을 사용합니다.
  • 부모 프로세스에서 __main__.__spec__이 설정되어 있을 때 자식 프로세스에서 __mp_main__을 생성하지 않도록 multiprocessing이 적절히 업데이트됩니다.

위의 첫 번째 항목이 이 PEP의 구체적인 제안을 다룹니다.

일반 모듈의 __name__은 더 이상 정식 이름이 아님

Chris Angelico는 이제 “import”에 지정한 이름과 __name__이 다른 모듈을 임포트할 수 있다고 지적합니다. “__main__”이 이제 “module.name”에 존재하므로 이후의 import module.name이 해당 모듈을 이미 존재하는 것으로 찾기 때문입니다. 따라서 일부 일반 임포트에서는 __name__이 더 이상 정식 이름이 아닙니다.

다음과 같은 반론이 있습니다.

  • PEP PEP 451부터 모듈의 정식 이름은 __spec__.name에 저장됩니다.
  • 실제로 __name__이 정식 이름인지에 신경 써야 하는 코드는 거의 없으며, 그러한 코드가 있다면 관련되는 경우 이전 Python 버전을 위해 __name__으로 대체하는 방식으로 __spec__.name을 참조하도록 업데이트하는 것이 타당합니다. 이 PEP가 승인되지 않더라도 이는 마찬가지입니다.
  • 이 PEP가 승인되면 정식 이름으로 모듈을 introspect하고, __name__에서 추론하여 “이것이 메인 프로그램인가?”라고 물을 수 있게 됩니다. 이전에는 이것이 불가능했습니다.

명백한 반례는 __name__이 “__main__”일 것으로 예상되는 표준적인 “내가 메인 프로그램인가?” 상용구 코드입니다. 이 PEP는 해당 의미를 명시적으로 보존합니다.

참조 구현

BPO 36375는 이 PEP의 참조 구현에 대한 이슈 추적 항목이며, 현재 초안 PR은 on GitHub에서 확인할 수 있습니다.

미해결 질문

이 제안은 하위 호환성에 관한 우려를 제기하며, 이를 충분히 이해한 후 지원 중단 절차를 설계하거나 명확한 포팅 지침을 제공해야 합니다.

피클 호환성

pickle 모듈을 변경하지 않으면, 이전에 이중 임포트로 인해 올바른 모듈 이름으로 기록되던 피클이 대신 __main__을 모듈 이름으로 기록하기 시작할 수 있으며, 그 결과 다른 프로젝트에서 올바르게 로드되지 않을 수 있습니다.

다음 시나리오를 확인해야 합니다.

  • python script.py로 기록하고 python -m script로 읽기
  • python -m script로 기록하고 python script.py로 읽기
  • python -m script로 기록하고 python some_other_app.py로 읽기
  • old_python -m script로 기록하고 new_python -m script로 읽기
  • new_python -m script 쓰기, old_python -m script 읽기

__main__을 특별 취급하는 프로젝트

회귀 테스트 모음을 통과시키기 위해 현재 참조 구현은 자체 전역 네임스페이스가 손상되는 것을 방지하도록 pdb를 패치해야 했습니다.

이는 일부 스크립트가 직접 실행과 임포트에서 서로 다른 네임스페이스를 제공하는 것에 의존하는 더 광범위한 호환성 문제가 있을 수 있음을 시사합니다(패키지 실행이 __main__네임스페이스에서 __main__서브모듈을 실행하여 두 네임스페이스를 분리하는 반면, 패키지 이름은 평소처럼 __init__파일을 참조하는 것과 같습니다.

배경

주 프로그램 모듈인 명명된 모듈을 몽키 패치하려는 모듈을 통해 주 프로그램을 디버깅하던 중 I tripped over this issue를 겪었습니다. 당연히 해당 모듈은 주 모듈을 이름으로 임포트했으므로 몽키 패치는 효과가 없었고, 실행 중인 모듈 인스턴스가 아니라 두 번째 모듈 인스턴스를 패치했습니다.

그러나 이 문제는 -m 명령줄 옵션이 존재한 시점부터 있었으며, 다른 사람들도 드물지만 정기적으로 이 문제를 접합니다.

issue 19702 외에도 __main__을 둘러싼 불일치는 PEP 451에서 암시적으로 언급되며, 이와 유사한 제안( PEP 451보다 앞선 제안)은 PEP 395Fixing dual imports of the main module에서 설명됩니다.

참고 자료