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

Python 개선 제안 한국어 번역

PEP 338 – 스크립트로 모듈 실행

Author:
Alyssa Coghlan <ncoghlan at gmail.com>
Status:
Final
Type:
Standards Track
Created:
16-Oct-2004
Python-Version:
2.5
Post-History:
08-Nov-2004, 11-Feb-2006, 12-Feb-2006, 18-Feb-2006

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 -m 명령줄 스위치를 사용하거나 runpy.run_module(modulename)을 호출하여 모든 Python 모듈을 스크립트로 실행하는 의미론을 정의합니다.

Python 2.4에 구현된 -m 스위치는 상당히 제한적입니다. 이 PEP는 코드 객체에 대한 접근을 제공하는 모든 모듈을 실행할 수 있도록 PEP 302 임포트 훅을 사용할 것을 제안합니다.

근거

Python 2.4에는 Python 모듈 네임스페이스를 사용하여 모듈을 찾아 스크립트로 실행할 수 있도록 명령줄 스위치 -m이 추가되었습니다. 동기가 된 예로는 pdbprofile과 같은 표준 라이브러리 모듈이 있었으며, Python 2.4의 구현은 이 제한된 목적에는 적합합니다.

여러 사용자와 개발자가 패키지 내부에 있는 모듈의 실행도 지원하도록 이 기능을 확장해 달라고 요청했습니다. 제시된 한 가지 예는 pychecker의 pychecker.checker 모듈입니다. 이 기능은 구현이 훨씬 더 복잡했고 가장 적절한 전략이 전혀 명확하지 않았기 때문에 Python 2.4 구현에서 제외되었습니다.

python-dev의 의견은 이 확장을 Python 2.5로 미루고, 올바르게 구현되도록 PEP 절차를 거치는 편이 낫다는 것이었습니다.

그 이후로 현재 버전의 -mzipimport나 다른 종류의 대체 임포트 동작(예: 동결된 모듈)을 지원하지 않는다는 점도 지적되었습니다.

이 기능을 C로 작성하는 것보다 Python 모듈로 제공하는 것이 훨씬 쉽고, CPython 인터프리터에만 한정하지 않고 모든 Python 프로그램에서 이 기능을 쉽게 사용할 수 있게 합니다. 그러면 CPython의 명령줄 스위치를 새 모듈을 사용하도록 다시 작성할 수 있습니다.

다른 스크립트를 실행하는 스크립트(예: profile, pdb)도 새 모듈을 사용하여 실행할 스크립트를 식별하는 -m 방식의 지원을 제공할 수 있습니다.

이 제안의 범위

Python 2.4에서는 -m을 사용하여 찾은 모듈이 해당 파일 이름이 명령줄에 제공된 것처럼 실행됩니다. 이 PEP의 목표는 패키지 내부의 모듈이나 대체 임포트 메커니즘(예: zipimport)을 통해 접근하는 모듈에도 이 설명이 최대한 그대로 적용되도록 하는 것입니다.

이전 논의에 따르면 이 PEP가 Python 모듈을 스크립트로도 유용하게 만드는 관용구를 변경하는 것과는 관련이 없다는 점을 명시해야 합니다(PEP 299 참조). 이 문제는 이 PEP가 다루는 특정 기능과는 별개의 것으로 간주됩니다.

현재 동작

새로운 의미론을 설명하기 전에 Python 2.4의 기존 의미론을 다루는 것이 좋습니다(현재 이 의미론은 소스 코드와 명령줄 도움말에 의해서만 정의되어 있기 때문입니다).

명령줄에서 -m을 사용하면 옵션 목록이 즉시 종료됩니다(-c와 같습니다). 인자는 최상위 Python 모듈의 이름으로 해석됩니다(즉, sys.path에서 찾을 수 있는 모듈입니다).

모듈이 발견되고 PY_SOURCE 또는 PY_COMPILED 유형이면, 명령줄은 사실상 python <options> -m <module> <args>에서 python <options> <filename> <args>로 다시 해석됩니다. 여기에는 sys.argv[0]을 올바르게 설정하는 것도 포함됩니다(일부 스크립트는 이에 의존하며, Python 자체의 regrtest.py가 한 예입니다).

모듈을 찾을 수 없거나 올바른 유형이 아니면 오류가 출력됩니다.

제안된 의미론

제안된 의미론은 매우 간단합니다. -m을 사용하여 모듈을 실행하면 PEP 302 임포트 메커니즘을 사용하여 모듈을 찾고 컴파일된 코드를 가져온 다음, 최상위 모듈의 의미론에 따라 모듈을 실행합니다. 인터프리터는 새로운 표준 라이브러리 함수 runpy.run_module을 호출하여 이를 수행합니다.

이는 Python의 임포트 기능이 패키지 내부의 모듈을 찾는 방식 때문에 필요합니다. 패키지는 초기화 중에 자체 __path__ 변수를 수정할 수 있습니다. 또한 경로는 *.pth 파일의 영향을 받을 수 있으며, 일부 패키지는 sys.metapath에 사용자 지정 로더를 설치합니다. 따라서 Python이 모듈을 신뢰성 있게 찾을 수 있는 유일한 방법은 모듈을 포함하는 패키지를 임포트하고 PEP 302 임포트 훅을 사용하여 Python 코드에 접근하는 것입니다.

실행할 모듈을 찾는 과정에서 모듈을 포함하는 패키지를 임포트해야 할 수 있다는 점에 유의하십시오. 그러한 패키지 임포트의 영향 중 실행된 모듈에서 볼 수 있는 것은 다음과 같습니다.

  • 모듈을 포함하는 패키지가 sys.modules에 들어갑니다.
  • 패키지 초기화의 모든 외부 영향(예: 설치된 임포트 훅, 로거, atexit 핸들러 등)이 적용됩니다.

참조 구현

참조 구현은 SourceForge([2])에서 제공되며, 라이브러리 참조 문서([5])도 함께 제공됩니다. 이 구현은 두 부분으로 구성됩니다. 첫 번째는 제안된 표준 라이브러리 모듈 runpy입니다. 두 번째는 -m 스위치를 구현하는 코드를 수정하여 모듈을 직접 실행하려 하지 않고 항상 runpy.run_module에 위임하도록 하는 것입니다. 위임은 다음과 같은 형식을 취합니다.:

runpy.run_module(sys.argv[0], run_name="__main__", alter_sys=True)

run_modulerunpy가 공개 API에서 노출하는 유일한 함수입니다.

run_module(mod_name[, init_globals][, run_name][, alter_sys])

지정된 모듈의 코드를 실행하고 그 결과인 모듈 전역 딕셔너리를 반환합니다. 먼저 표준 임포트 메커니즘을 사용하여 모듈의 코드를 찾고(PEP 302 참조), 그런 다음 새 모듈 네임스페이스에서 실행합니다.

선택적 딕셔너리 인자 init_globals를 사용하여 코드가 실행되기 전에 전역 딕셔너리를 미리 채울 수 있습니다. 제공된 딕셔너리는 수정되지 않습니다. 아래의 특수 전역 변수가 제공된 딕셔너리에 정의되어 있으면 해당 정의는 run_module 함수에 의해 덮어써집니다.

특수 전역 변수 __name__, __file__, __loader____builtins__은 모듈 코드가 실행되기 전에 전역 딕셔너리에 설정됩니다.

이 선택적 인자가 제공되면 __name__run_name으로 설정되고, 그렇지 않으면 원래 mod_name 인자로 설정됩니다.

__loader__는 모듈의 코드를 가져오는 데 사용된 PEP 302 모듈 로더로 설정됩니다(이 로더는 표준 임포트 메커니즘을 감싸는 래퍼일 수 있습니다).

__file__은 모듈 로더가 제공한 이름으로 설정됩니다. 로더가 파일 이름 정보를 제공하지 않으면 이 인자는 None으로 설정됩니다.

__builtins__은 자동으로 초기화되며, 이는 __builtin__ 모듈의 최상위 네임스페이스에 대한 참조를 포함합니다.

alter_sys 인자가 제공되고 그 값이 True로 평가되면 sys.argv[0]__file__의 값으로 업데이트되고, sys.modules[__name__]은 실행 중인 모듈을 위한 임시 모듈 객체로 업데이트됩니다. 이 함수가 반환되기 전에 sys.argv[0]sys.modules[__name__]은 모두 원래 값으로 복원됩니다.

스크립트로 호출되면 runpy 모듈은 첫 번째 인자로 제공된 모듈을 찾아 실행합니다. 그런 다음 runpy 모듈 자체를 가리키는 sys.argv[0]을 삭제하여 sys.argv를 조정하고, run_module(sys.argv[0], run_name="__main__", alter_sys=True)를 호출합니다.

임포트 문과 메인 모듈

2.5b1 릴리스는 이 PEP와 PEP 328 사이의 놀라운 (돌이켜 보면 명백하기는 하지만) 상호작용을 드러냈습니다 - 명시적 상대 임포트는 메인 모듈에서 동작하지 않습니다. 이는 상대 임포트가 패키지 계층 구조 내에서 현재 모듈의 위치를 결정하기 위해 __name__에 의존한다는 사실 때문입니다. 메인 모듈에서 __name__의 값은 항상 '__main__'이므로, 명시적 상대 임포트는 (패키지 내부의 모듈에서만 동작하기 때문에) 항상 실패합니다.

메인 모듈을 직접 실행할 때는 암시적 상대 임포트가 동작하는 것처럼 보이지만 -m을 사용해 실행하면 실패하는 이유를 조사한 결과, 그러한 임포트는 실제로는 항상 절대 임포트로 취급된다는 것이 밝혀졌습니다. 직접 실행이 동작하는 방식 때문에, 실행되는 모듈을 포함하는 패키지가 sys.path에 추가되므로, 그 형제 모듈들은 실제로 최상위 모듈로서 임포트됩니다. 직접 실행될 수 있는 모듈(예: 테스트 모듈이나 유틸리티 스크립트)에서 암시적 상대 임포트를 사용하면, 애플리케이션 내에 형제 모듈들의 복사본이 여러 개 생기기 쉽습니다.

2.5 릴리스에서는 메인 모듈로 사용될 것으로 의도된 모든 모듈에서 항상 절대 임포트를 사용할 것을 권장합니다. -m 스위치는 메인 모듈을 포함하는 디렉터리 대신 현재 디렉터리를 sys.path에 삽입하므로 여기서 이점을 제공합니다. 즉, 현재 디렉터리가 패키지의 최상위 디렉터리를 포함하고 있는 한, -m을 사용해 패키지 내부의 모듈을 실행하는 것이 가능하다는 뜻입니다. 패키지가 sys.path의 다른 어느 곳에도 설치되어 있지 않더라도 절대 임포트는 올바르게 동작합니다. 모듈이 직접 실행되며 절대 임포트를 사용해 형제 모듈들을 가져오는 경우, (현재 디렉터리는 자동으로 추가되지 않으므로) 최상위 패키지 디렉터리를 sys.path 어딘가에 설치해 두어야 합니다.

다음은 예시 파일 배치입니다:

devel/
    pkg/
        __init__.py
        moduleA.py
        moduleB.py
        test/
            __init__.py
            test_A.py
            test_B.py

현재 디렉터리가 devel이거나, devel이 이미 sys.path에 있고 테스트 모듈들이 절대 임포트(테스트 대상 모듈을 가져오기 위한 import pkg moduleA와 같은)를 사용하는 한, PEP 338은 테스트를 다음과 같이 실행할 수 있도록 허용합니다:

python -m pkg.test.test_A
python -m pkg.test.test_B

메인 모듈이 -m으로 실행될 때 상대 임포트를 지원해야 하는지 여부에 대한 문제는 Python 2.6을 위해 다시 검토될 사안입니다. 이를 허용하려면 Python의 임포트 시맨틱이나 모듈이 메인 모듈임을 나타내는 데 사용되는 시맨틱 중 하나를 변경해야 하므로, 이는 성급하게 내릴 결정이 아닙니다.

해결된 문제들

runpy 모듈의 개발에 영향을 준 몇 가지 핵심 설계 결정이 있었습니다. 아래에 이를 나열합니다.

  • 특수 변수 __name__, __file____loader__는 모듈이 실행되기 전에 그 모듈의 전역 네임스페이스에 설정됩니다. run_module은 이 값들을 변경하지만, 전달받은 딕셔너리를 변형시키지는 않습니다. 만약 그렇게 했다면, globals()를 이 함수에 전달하는 것이 좋지 않은 부작용을 일으킬 수 있었을 것입니다.
  • 때로는 특수 변수들을 채우는 데 필요한 정보를 전혀 얻을 수 없는 경우가 있습니다. 지나치게 영리해지려 하기보다는, 관련 정보를 알아낼 수 없을 때 이 변수들은 단순히 None으로 설정됩니다.
  • alter_sys 인자에는 특별한 보호 장치가 없습니다. 파일 이름 정보를 사용할 수 없는 경우, 이로 인해 sys.argv[0]None으로 설정될 수 있습니다.
  • alter_sys가 True로 설정될 때 발생할 수 있는 스레딩 문제를 피하기 위해 임포트 잠금은 사용되지 않습니다. 대신, 스레드를 사용하는 코드는 이 플래그의 사용을 그저 피할 것을 권장합니다.

대안들

첫 번째로 고려된 대안 구현은 패키지의 __path__ 변수를 무시하고 메인 패키지 디렉터리만을 살펴보는 것이었습니다. 이러한 동작을 하는 Python 스크립트는 execmodule 쿡북 레시피 [3]에 대한 논의에서 찾아볼 수 있습니다.

execmodule 쿡북 레시피 자체는 (이 PEP의 저자가 PEP 302를 읽기 전) 이 PEP의 이전 버전에서 제안되었던 메커니즘이었습니다.

두 접근 방식 모두, 명령줄에서 실행할 모듈을 찾는 데 전체 Python 네임스페이스를 사용할 수 있도록 한다는 -m 스위치의 주된 목표를 충족하지 못했기 때문에 거부되었습니다.

이 PEP의 이전 버전에는 exec가 함수 객체로부터의 지역 변수 딕셔너리와 코드를 처리하는 방식에 대한 잘못된 가정이 일부 포함되어 있었습니다. 이러한 잘못된 가정으로 인해 불필요한 설계 복잡성이 생겼으나 이제는 제거되었습니다 - run_codeexec의 모든 특이점을 공유합니다.

이 PEP의 이전 버전들은 -m 스위치에 대한 업데이트를 구현하는 데 필요한 단일 run_module() 함수보다 더 넓은 API를 노출하기도 했습니다. 단순성을 위해 그러한 추가 함수들은 제안된 API에서 제외되었습니다.

SVN에서의 원래 구현 이후, 초기 애플리케이션 스크립트를 실행할 때 임포트 잠금을 유지하는 것이 올바르지 않다는 것이 명확해졌습니다(예: python -m test.regrtest test_threadedimport가 실패함). 따라서 run_module 함수는 모듈을 실제로 검색하는 동안에만 임포트 잠금을 유지하며, alter_sys가 설정되어 있더라도 실행 전에 잠금을 해제합니다.

참고 자료