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

Python 개선 제안 한국어 번역

PEP 302 – 새 임포트 훅

Author:
Just van Rossum <just at letterror.com>, Paul Moore <p.f.moore at gmail.com>
Status:
Final
Type:
Standards Track
Created:
19-Dec-2002
Python-Version:
2.3
Post-History:
19-Dec-2002

Table of Contents

번역·라이선스 안내

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

Warning

import 언어 레퍼런스 [10]와 importlib 문서 [11]가 이제 이 PEP를 대체합니다. 이 문서는 더 이상 업데이트되지 않으며 역사적 목적만을 위해 제공됩니다.

초록

이 PEP는 Python 임포트 메커니즘을 더욱 효과적으로 사용자 지정할 수 있는 새로운 임포트 훅 집합을 추가할 것을 제안합니다. 현재의 __import__ 훅과 달리 새 스타일 훅은 기존 체계에 삽입할 수 있으므로, 모듈을 찾고 로드하는 방식을 더욱 세밀하게 제어할 수 있습니다.

동기

현재 임포트 메커니즘을 사용자 지정하는 유일한 방법은 내장 __import__ 함수를 재정의하는 것입니다. 그러나 __import__를 재정의하면 많은 문제가 발생합니다. 우선 다음과 같습니다.

  • __import__ 대체 구현은 전체 임포트 메커니즘을 완전히 다시 구현하거나, 사용자 지정 코드 전이나 후에 원래 __import__를 호출해야 합니다.
  • 의미 체계와 책임이 매우 복잡합니다.
  • 이미 sys.modules에 있는 모듈에 대해서도 __import__가 호출되는데, 어떤 종류의 모니터링 도구를 작성하는 경우가 아니라면 이는 거의 대부분 원하지 않는 동작입니다.

C에서 임포트 메커니즘을 확장해야 하는 경우 상황은 더 나빠집니다. 현재로서는 Python의 import.c를 해킹하거나 import.c의 상당 부분을 처음부터 다시 구현하는 방법 외에는 불가능합니다.

__import__ 훅을 기반으로 다양한 방식으로 임포트 메커니즘을 확장할 수 있도록 Python으로 작성된 도구의 역사는 상당히 깁니다. 표준 라이브러리에는 이러한 도구가 두 개 포함되어 있습니다. GvR가 작성한 ihooks.py와 Greg Stein이 작성한 imputil.py [1]입니다. 그러나 아마도 가장 유명한 것은 Gordon McMillan이 작성했으며 그의 Installer 패키지의 일부로 제공되는 iu.py일 것입니다. 이러한 도구는 Python으로 작성되었기 때문에 유용성이 다소 제한됩니다. 훅 자체로 훅을 포함하는 모듈을 로드할 수 없으므로 부트스트래핑 문제를 우회해야 합니다. 따라서 전체 표준 라이브러리를 임포트 훅에서 로드할 수 있게 하려면 훅을 C로 작성해야 합니다.

사용 사례

이 절에서는 임포트 훅에 의존하는 기존 애플리케이션 몇 가지를 나열합니다. 이들 가운데 많은 중복 작업이 수행되었으며, 당시 더 유연한 임포트 훅이 있었다면 이를 피할 수 있었을 것입니다. 이 PEP는 앞으로 유사한 프로젝트를 훨씬 수월하게 진행할 수 있도록 해야 합니다.

비표준적인 방식으로 저장된 모듈을 로드하려면 임포트 메커니즘을 확장해야 합니다. 예를 들면 아카이브에 함께 묶여 있는 모듈, pyc 형식의 파일에 저장되지 않은 바이트코드, 네트워크를 통해 데이터베이스에서 로드되는 모듈이 있습니다.

이 PEP에 대한 작업은 부분적으로 PEP 273의 구현에 의해 촉발되었습니다. 이 구현은 Zip 아카이브에서 임포트하는 기능을 Python의 내장 기능으로 추가합니다. PEP 자체는 필수 기능으로 널리 받아들여졌지만, 구현에는 몇 가지 아쉬운 점이 남았습니다. 우선 이 구현은 import.c와 통합하기 위해 상당한 노력을 기울였으며, Zip 파일 임포트에 특화된 코드나 Zip 임포트에 특화되지 않은 코드를 대량으로 추가했습니다. 그러나 어느 쪽도 일반적으로 유용하지 않았고, 심지어 바람직하지도 않았습니다. 그렇다고 PEP 273의 구현을 탓하기는 어렵습니다. 현재 import.c의 상태를 고려하면 이는 단순히 구현하기가 매우 어렵기 때문입니다.

최종 사용자를 위한 애플리케이션 패키징은 임포트 훅의 전형적인 사용 사례이며, 가장 전형적인 사용 사례라고 해도 과언이 아닙니다. 많은 소스 파일이나 pyc 파일을 배포하는 것은 항상 적절하지 않으며, 별도의 Python 설치는 더더욱 그렇습니다. 따라서 필요한 모든 모듈을 하나의 파일에 패키징하려는 요구가 자주 발생합니다. 실제로 그 빈도가 높아서 수년에 걸쳐 여러 해결책이 구현되었습니다.

가장 오래된 것은 Python 소스 코드에 포함되어 있습니다: Freeze [2]. 이는 마샬링된 바이트코드를 C 소스 코드의 정적 객체에 넣습니다. Freeze의 “import hook”은 import.c에 하드 코딩되어 있으며, 몇 가지 문제가 있습니다. 이후의 해결책으로는 Fredrik Lundh의 Squeeze, Gordon McMillan의 Installer, Thomas Heller의 py2exe [3]가 있습니다. MacPython에는 BuildApplication이라는 도구가 함께 제공됩니다.

Squeeze, Installer 및 py2exe는 __import__ 기반 방식을 사용하고(py2exe는 현재 Installer의 iu.py를 사용하며 Squeeze는 ihooks.py를 사용합니다), MacPython에는 Freeze 훅과 유사한 Mac 전용 import hook 두 개가 import.c에 하드 코딩되어 있습니다. 이 PEP에서 제안하는 훅을 사용하면 (적어도 이론적으로는 가능하지만 단기 목표는 아닙니다) import.c의 하드 코딩된 훅을 제거할 수 있으며, __import__ 기반 도구가 import.c 에뮬레이션 코드 대부분을 제거할 수 있게 됩니다.

이 PEP의 설계와 구현 작업이 시작되기 전에 Mac OS X용 새로운 BuildApplication 유사 도구가 이 PEP의 저자 중 한 명(JvR)에게 imp 모듈에서 동결된 모듈의 테이블을 Python에 노출하도록 촉구했습니다. 주된 이유는 freeze import hook을 사용할 수 있게 하면서(복잡한 __import__ 지원을 피하면서도) 런타임에 모듈 집합을 제공할 수 있게 하기 위해서였습니다. 이로 인해 issue #642578 [4]가 발생했고, 이 이슈는 불가사의하게 받아들여졌습니다(대부분 어느 쪽이든 아무도 신경 쓰지 않는 듯했기 때문입니다 ;-). 그러나 이 PEP가 받아들여지면 이는 완전히 불필요해집니다. 동일한 작업을 훨씬 더 좋고 일반적인 방식으로 수행할 수 있게 해 주기 때문입니다.

근거

내장 Zip import를 구현하는 대안적인 방법을 실험하던 중, import.c를 상당히 조금만 변경해도 이를 달성할 수 있다는 사실이 밝혀졌습니다. 이를 통해 Zip 관련 부분을 새 소스 파일로 분리하는 동시에, 일반적인 새 import hook 방식을 만들 수 있었습니다. 바로 지금 읽고 계신 방식입니다.

이전 설계에서는 sys.path에 문자열이 아닌 객체를 허용했습니다. 그러한 객체에는 import를 처리하는 데 필요한 메서드가 있어야 했습니다. 여기에는 두 가지 단점이 있습니다. 1) sys.path의 모든 항목이 문자열이라고 가정하는 코드가 손상됩니다. 2) PYTHONPATH 환경 변수와 호환되지 않습니다. 후자는 Zip import에 직접 필요합니다. Jython에서 절충안이 나왔습니다. sys.path에 문자열 서브클래스를 허용하고, 그러면 해당 서브클래스가 importer 객체로 동작하게 하는 것입니다. 이렇게 하면 일부 호환성 문제가 방지되고 Jython에서도 잘 작동하는 것으로 보이지만(.jar 파일에서 모듈을 로드하는 데 사용됩니다), 이는 “추한 해킹”으로 인식되었습니다.

이로 인해 더 정교한 방식이 도입되었습니다(iu.py에 있는 McMillan의 방식을 대부분 복사한 것입니다). 이 방식에서는 후보 목록의 각 항목에 sys.path 항목을 처리할 수 있는지 묻고, 처리할 수 있는 항목을 찾을 때까지 이를 반복합니다. 이 후보 목록은 sys 모듈의 새로운 객체입니다: sys.path_hooks.

각각의 새 import마다 각 경로 항목에 대해 sys.path_hooks를 순회하는 것은 비용이 많이 들 수 있으므로, 결과를 sys 모듈의 또 다른 새 객체인 sys.path_importer_cache에 캐시합니다. 이는 sys.path 항목을 importer 객체에 매핑합니다.

import.c에 미치는 영향을 최소화하고 추가 오버헤드를 피하기 위해, 기존 파일 시스템 import 로직에 명시적인 hook과 importer 객체를 추가하지 않고(iu.py에는 추가되어 있습니다), sys.path_hooks의 어떤 hook도 경로 항목을 처리할 수 없으면 단순히 내장 로직으로 폴백하도록 선택했습니다. 이 경우 반복적인 조회를 피하기 위해 sys.path_importer_cacheNone 값을 저장합니다. (나중에는 더 나아가 내장 메커니즘을 위한 실제 importer 객체를 추가할 수 있지만, 현재로서는 None 폴백 방식으로 충분할 것입니다.)

다음과 같은 질문이 제기되었습니다. sys.path에 항목이 전혀 필요하지 않은 importer는 어떻게 합니까? (내장 모듈과 동결된 모듈이 이 범주에 속합니다.) 이번에도 Gordon McMillan이 해결책을 제시했습니다. iu.py에는 그가 메타 경로라고 부르는 것이 들어 있습니다. 이 PEP의 구현에서는 이것이 sys.path 앞에서 순회되는 importer 객체 목록입니다. 이 목록은 sys 모듈의 또 다른 새로운 객체입니다: sys.meta_path. 현재 이 목록은 기본적으로 비어 있으며, 동결된 모듈과 내장 모듈의 import는 sys.meta_path를 순회한 후에 수행되지만 여전히 sys.path보다 먼저 수행됩니다.

사양 1부: Importer 프로토콜

이 PEP는 새로운 프로토콜인 “Importer Protocol”을 도입합니다. 프로토콜이 작동하는 맥락을 이해하는 것이 중요하므로, 여기에서는 임포트 메커니즘의 외부 계층을 간략히 개요로 설명합니다.

임포트 문을 만나면 인터프리터는 내장 네임스페이스에서 __import__ 함수를 조회합니다. 그런 다음 __import__는 네 개의 인자와 함께 호출되며, 그중에는 임포트되는 모듈의 이름(점으로 구분된 이름일 수 있음)과 현재 전역 네임스페이스에 대한 참조가 포함됩니다.

내장 __import__ 함수(import.c에서 PyImport_ImportModuleEx()로 알려짐)는 임포트를 수행하는 모듈이 패키지인지 패키지의 서브모듈인지 확인합니다. 실제로 패키지(또는 패키지의 서브모듈)라면, 먼저 패키지(서브모듈의 경우 부모 패키지)에 상대적인 임포트를 시도합니다. 예를 들어 “spam”이라는 패키지가 “import eggs”를 수행하면, 먼저 “spam.eggs”라는 모듈을 찾습니다. 실패하면 임포트는 절대 임포트로 계속되며, “eggs”라는 모듈을 찾습니다. 점으로 구분된 이름의 임포트도 거의 동일하게 작동합니다. 패키지 “spam”이 “import eggs.bacon”을 수행하고(“spam.eggs”가 존재하며 그 자체가 패키지인 경우), “spam.eggs.bacon”을 시도합니다. 실패하면 “eggs.bacon”을 시도합니다. (여기에서 설명하지 않은 세부 사항이 더 있지만, 이는 Importer 프로토콜 구현자에게는 관련이 없습니다.)

메커니즘의 더 깊은 단계에서는 점으로 구분된 이름의 임포트를 구성 요소별로 나눕니다. “import spam.ham”의 경우 먼저 “import spam”을 수행하며, 이것이 성공한 경우에만 “ham”을 “spam”의 서브모듈로 임포트합니다.

Importer 프로토콜은 개별 가져오기 수준에서 작동합니다. 임포터가 “spam.ham”에 대한 요청을 받을 때쯤이면 “spam” 모듈은 이미 임포트된 상태입니다.

이 프로토콜에는 finderloader라는 두 객체가 관여합니다. 파인더 객체에는 단일 메서드가 있습니다.:

finder.find_module(fullname, path=None)

이 메서드는 모듈의 정규화된 이름과 함께 호출됩니다. 파인더가 sys.meta_path에 설치되어 있으면 두 번째 인자를 받습니다. 최상위 모듈에서는 None이고, 서브모듈 또는 서브패키지에서는 package.__path__입니다 [5]. 모듈을 찾았으면 로더 객체를 반환하고, 찾지 못했으면 None을 반환해야 합니다. find_module()이 예외를 발생시키면 해당 예외는 호출자에게 전파되어 임포트를 중단합니다.

로더 객체에도 메서드가 하나 있습니다.:

loader.load_module(fullname)

이 메서드는 로드된 모듈을 반환하거나 예외를 발생시키며, 기존 예외가 전파되고 있지 않다면 가급적 ImportError를 발생시킵니다. load_module()에 로드할 수 없는 모듈을 로드하도록 요청하면 ImportError를 발생시켜야 합니다.

많은 경우 파인더와 로더는 하나의 동일한 객체일 수 있습니다. finder.find_module()이 단순히 self를 반환하면 됩니다.

두 메서드의 fullname 인자는 모듈의 정규화된 이름이며, 예를 들어 “spam.eggs.ham”입니다. 위에서 설명한 것처럼 finder.find_module("spam.eggs.ham")이 호출될 때 “spam.eggs”는 이미 임포트되어 sys.modules에 추가된 상태입니다. 그러나 실제 가져오기 중에 find_module() 메서드가 항상 호출되는 것은 아닙니다. 가져오기 의존성을 분석하는 메타 도구(예: freeze, Installer 또는 py2exe)는 실제로 모듈을 로드하지 않으므로, 파인더는 부모 패키지가 sys.modules에 존재한다고 의존해서는 안 됩니다.

load_module()메서드는 코드를 실행하기전에 충족해야 하는 몇 가지 책임이 있습니다.

  • sys.modules에 ‘fullname’이라는 이름의 기존 모듈 객체가 있으면 로더는 해당 기존 모듈을 사용해야 합니다. (그렇지 않으면 내장 reload()가 올바르게 작동하지 않습니다.) sys.modules에 ‘fullname’이라는 이름의 모듈이 없으면 로더는 새 모듈 객체를 생성하여 sys.modules에 추가해야 합니다.

    로더가 모듈 코드를 실행하기 전에 모듈 객체가 sys.modules반드시 있어야 한다는 점에 유의하십시오. 이는 모듈 코드가 직접 또는 간접적으로 자기 자신을 임포트할 수 있기 때문에 중요합니다. 미리 sys.modules에 추가하면 최악의 경우 무한 재귀를, 최선의 경우 여러 번 로드되는 것을 방지합니다.

    로드에 실패하면 로더는 자신이 sys.modules에 삽입했을 수 있는 모든 모듈을 제거해야 합니다. 모듈이 이미 sys.modules에 있었다면 로더는 해당 모듈을 그대로 두어야 합니다.

  • __file__ 특성을 설정해야 합니다. 이는 문자열이어야 하지만, “<frozen>”과 같은 더미 값이어도 됩니다. __file__ 특성이 전혀 없어도 되는 특권은 내장 모듈에만 부여됩니다.
  • __name__ 특성을 설정해야 합니다. imp.new_module()을 사용하면 해당 특성이 자동으로 설정됩니다.
  • 패키지인 경우 __path__ 변수를 설정해야 합니다. 이는 리스트여야 하지만, __path__가 임포터에 더 이상 의미가 없다면 비어 있어도 됩니다.
  • __loader__ 특성을 로더 객체로 설정해야 합니다. 이는 주로 인트로스펙션과 다시 로드하기 위한 것이지만, 임포터별 추가 기능, 예를 들어 임포터와 연결된 데이터를 가져오는 데 사용할 수 있습니다.
  • __package__ 특성을 설정해야 합니다 (PEP 366).

    모듈이 Python 모듈인 경우(내장 모듈이나 동적으로 로드된 확장 모듈과는 반대로), 모듈의 전역 네임스페이스(module.__dict__)에서 모듈의 코드를 실행해야 합니다.

    다음은 load_module() 메서드의 최소 패턴입니다.:

    # Consider using importlib.util.module_for_loader() to handle
    # most of these details for you.
    def load_module(self, fullname):
        code = self.get_code(fullname)
        ispkg = self.is_package(fullname)
        mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
        mod.__file__ = "<%s>" % self.__class__.__name__
        mod.__loader__ = self
        if ispkg:
            mod.__path__ = []
            mod.__package__ = fullname
        else:
            mod.__package__ = fullname.rpartition('.')[0]
        exec(code, mod.__dict__)
        return mod
    

사양 2부: 훅 등록

임포트 훅에는 메타 훅경로 훅이라는 두 가지 유형이 있습니다. 메타 훅은 임포트 처리 시작 시 다른 모든 임포트 처리보다 먼저 호출됩니다(따라서 메타 훅은 sys.path 처리, 동결된 모듈 또는 내장 모듈까지 재정의할 수 있습니다). 메타 훅을 등록하려면 파인더 객체를 sys.meta_path(등록된 메타 훅의 리스트)에 추가하기만 하면 됩니다.

경로 훅은 sys.path(또는 package.__path__) 처리의 일부로, 해당 경로 항목이 발견되는 시점에 호출됩니다. 경로 훅은 임포터 팩토리를 sys.path_hooks에 추가하여 등록합니다.

sys.path_hooks는 호출 가능 객체의 리스트이며, 주어진 경로 항목을 처리할 수 있는지 확인하기 위해 순서대로 검사됩니다. 호출 가능 객체는 경로 항목이라는 하나의 인자를 받아 호출됩니다. 호출 가능 객체는 경로 항목을 처리할 수 없으면 ImportError를 발생시켜야 하며, 처리할 수 있으면 임포터 객체를 반환해야 합니다. 호출 가능 객체가 특정 sys.path 항목에 대해 임포터 객체를 반환하면, 이후 해당 임포터 객체가 특정 모듈을 찾지 못하더라도 내장 임포트 메커니즘은 더 이상 그 항목을 처리하기 위해 호출되지 않는다는 점에 유의하십시오. 호출 가능 객체는 일반적으로 임포트 훅의 클래스이므로 해당 클래스의 __init__() 메서드가 호출됩니다. (이는 __init__() 메서드가 아무것도 반환할 수 없으므로 ImportError를 발생시켜야 하는 이유이기도 합니다. 새 스타일 클래스의 __new__() 메서드를 사용하면 가능하지만, 훅이 구현되는 방식에 관해 어떠한 것도 요구하고 싶지는 않습니다.)

경로 훅 검사 결과는 sys.path_importer_cache에 캐시되며, 이는 경로 항목을 임포터 객체에 매핑하는 딕셔너리입니다. 캐시를 sys.path_hooks를 검사하기 전에 확인합니다. sys.path_hooks를 다시 검사하도록 강제해야 하는 경우, sys.path_importer_cache의 전체 또는 일부를 수동으로 지울 수 있습니다.

sys.path 자체와 마찬가지로 새 sys 변수에도 특정 타입이 있어야 합니다.

  • sys.meta_pathsys.path_hooks는 Python 리스트여야 합니다.
  • sys.path_importer_cache는 Python 딕셔너리여야 합니다.

이러한 변수를 제자리에서 수정할 수 있으며, 새 객체로 교체할 수도 있습니다.

패키지와 __path__의 역할

모듈에 __path__ 속성이 있으면, 임포트 메커니즘은 이를 패키지로 취급합니다. 패키지의 서브모듈을 임포트할 때는 sys.path대신 __path__ 변수를 사용합니다. 따라서 sys.path에 대한 규칙은 pkg.__path__에도 적용됩니다. 그러므로 pkg.__path__를 순회할 때 sys.path_hooks도 참조합니다. 메타 임포터는 작업을 수행할 때 반드시 sys.path를 사용하는 것은 아니므로, pkg.__path__의 값을 무시할 수도 있습니다. 이 경우에도 이를 리스트로 설정하는 것이 권장되며, 리스트는 비어 있을 수 있습니다.

임포터 프로토콜의 선택적 확장

임포터 프로토콜은 세 가지 선택적 확장을 정의합니다. 하나는 데이터 파일을 검색하는 것이고, 두 번째는 모듈 패키징 도구 및/또는 모듈 의존성을 분석하는 도구(예: Freeze)를 지원하는 것이며, 마지막은 모듈을 스크립트로 실행하는 것을 지원하는 것입니다. 일반적으로 이 도구 범주 중 뒤의 두 가지는 실제로 모듈을 load하지는 않고, 모듈을 사용할 수 있는지와 어디에서 사용할 수 있는지만 알면 됩니다. 세 가지 확장 모두 범용 임포터에 강력히 권장되지만, 해당 기능이 필요하지 않다면 안전하게 생략할 수 있습니다.

기본 저장소 백엔드에서 임의의 “파일”에 대한 데이터를 검색하려면, 로더 객체는 get_data()라는 메서드를 제공할 수 있습니다:

loader.get_data(path)

이 메서드는 데이터를 문자열로 반환하거나, “파일”을 찾지 못한 경우 IOError를 발생시킵니다. 예를 들어 데이터는 항상 “바이너리” 모드를 사용한 것처럼 반환되므로 텍스트 파일의 CRLF 변환은 수행되지 않습니다. 이 기능은 일부 파일 시스템과 유사한 속성을 가진 임포터를 위한 것입니다. ‘path’ 인자는 module.__file__(또는 pkg.__path__항목)을 os.path.*함수로 조작하여 구성할 수 있는 경로입니다. 예를 들면:

d = os.path.dirname(__file__)
data = __loader__.get_data(os.path.join(d, "logo.gif"))

다음 메서드 집합은 (예를 들어) Freeze와 유사한 도구에 대한 지원이 필요할 경우 구현할 수 있습니다. 이는 호출자가 쉽게 사용할 수 있도록 세 가지 추가 메서드로 구성되며, 세 가지 모두 구현하거나 하나도 구현하지 않아야 합니다:

loader.is_package(fullname)
loader.get_code(fullname)
loader.get_source(fullname)

모듈을 찾지 못한 경우 세 메서드 모두 ImportError를 발생시켜야 합니다.

loader.is_package(fullname) 메서드는 ‘fullname’으로 지정된 모듈이 패키지이면 True를 반환하고, 패키지가 아니면 False를 반환해야 합니다.

loader.get_code(fullname) 메서드는 모듈과 연결된 코드 객체를 반환해야 하며, 내장 모듈이나 확장 모듈이면 None을 반환해야 합니다. 로더가 코드 객체를 가지고 있지 않지만 실제로 소스 코드를 가지고 있다면, 컴파일된 소스 코드를 반환해야 합니다. (이는 호출자에게 코드 객체만 필요할 경우 get_source()를 별도로 확인할 필요도 없도록 하기 위한 것입니다.)

loader.get_source(fullname) 메서드는 모듈의 소스 코드를 문자열로 반환해야 하며(줄 끝에는 줄 바꿈 문자를 사용함), 소스를 사용할 수 없으면 None을 반환해야 합니다. 단, 임포터가 모듈을 전혀 찾지 못하는 경우에는 여전히 ImportError를 발생시켜야 합니다.

모듈을 스크립트로 실행할 수 있도록 지원하려면 (PEP 338), 모듈과 연결된 코드를 찾기 위한 위의 세 메서드를 구현해야 합니다. 이러한 메서드 외에도 runpy 모듈이 __file__ 속성을 올바르게 설정할 수 있도록 다음 메서드를 제공할 수 있습니다.:

loader.get_filename(fullname)

이 메서드는 이름이 지정된 모듈이 로드되었을 때 __file__에 설정될 값을 반환해야 합니다. 모듈을 찾지 못한 경우 ImportError를 발생시켜야 합니다.

‘imp’ 모듈과의 통합

새로운 임포트 훅은 기존의 imp.find_module()imp.load_module() 호출에 쉽게 통합되지 않습니다. 코드를 손상하지 않고 가능한지조차 의문이므로, imp 모듈에 새 함수를 단순히 추가하는 편이 낫습니다. 기존 imp.find_module()imp.load_module() 호출의 의미가 “내장 임포트 메커니즘을 노출합니다”에서 “기본 후크되지 않은 내장 임포트 메커니즘을 노출합니다”로 바뀝니다. 해당 호출은 어떠한 임포트 훅도 호출하지 않습니다. imp 모듈에 새 함수를 get_loader()라는 이름으로 추가하는 방안이 제안되어 있으며(아직 구현되지는 않았습니다), 다음 패턴과 같이 사용됩니다.:

loader = imp.get_loader(fullname, path)
if loader is not None:
    loader.load_module(fullname)

imp.find_module() 함수가 처리할 “기본” 임포트의 경우, 로더 객체는 imp.find_module()의 현재 출력에 대한 래퍼가 되고, loader.load_module()은 해당 출력을 사용하여 imp.load_module()을 호출합니다.

이 래퍼는 현재 아직 구현되지 않았지만, 패치에 포함된 test_importhooks.py 스크립트에는 Python 프로토타입(ImpWrapper 클래스)이 존재한다는 점에 유의하십시오.

전방 호환성

기존 __import__ 훅은 원래 __import__ 함수를 대체 수단으로 호출하지 않는 한 새 스타일 훅을 자동으로 호출하지 않습니다. 예를 들어 ihooks.py, iu.pyimputil.py 는 이러한 의미에서 이 PEP와 전방 호환되지 않습니다.

미해결 문제

모듈은 작업을 수행하려면 지원 데이터 파일이 필요한 경우가 많으며, 특히 복잡한 패키지나 완전한 애플리케이션의 경우에는 더욱 그렇습니다. 현재는 일반적으로 sys.path(또는 package.__path__ 속성)를 통해 이러한 파일을 찾습니다. 이 방법은 일반적으로 임포트 훅을 통해 로드된 모듈에는 작동하지 않습니다.

이 문제를 해결할 수 있는 방법은 여러 가지가 있습니다.

  • “그렇게 하지 마십시오.” 패키지가 __path__을 통해 데이터 파일을 찾아야 한다면, 임포트 훅을 통한 로드에는 적합하지 않습니다. 현재처럼 해당 패키지는 여전히 sys.path의 디렉터리에서 찾을 수 있으므로, 이를 중대한 문제로 보아서는 안 됩니다.
  • 모듈 파일을 기준으로 찾지 말고 표준 위치에서 데이터 파일을 찾으십시오. 비교적 간단한 방법(distutils가 지원함)은 sys.prefix(또는 sys.exec_prefix)를 기반으로 데이터 파일을 찾는 것입니다. 예를 들어 os.path.join(sys.prefix, "data", package_name)을 살펴보는 방식입니다.
  • 임포트 훅은 모듈 파일을 기준으로 데이터 파일에 접근하는 표준 방법을 제공할 수 있습니다. 표준 zipimport 객체는 get_data(name) 메서드를 제공하며, 이 메서드는 name 이라는 “파일”의 내용을 문자열로 반환합니다. 모듈이 임포터 객체에 접근할 수 있도록 zipimport 는 모듈에 __loader__ 속성도 추가하며, 이 속성에는 모듈을 로드하는 데 사용된 zipimport 객체가 포함됩니다. 이러한 방법을 사용할 경우 get_data() 메서드를 사용할 수 없더라도 클라이언트 코드가 중단되지 않도록 주의해야 하므로, 이 방법이 문제에 대한 일반적인 해답을 제공하는지는 분명하지 않습니다.

python-dev에서는 임포터로부터 사용 가능한 모듈 목록 및/또는 get_data() 메서드에 사용할 수 있는 데이터 파일 목록을 받을 수 있으면 유용할 것이라는 제안이 있었습니다. 프로토콜에는 list_modules()list_files()와 같은 두 가지 추가 확장을 추가할 수 있습니다. 후자는 get_data() 메서드를 가진 로더 객체에서 의미가 있습니다. 그러나 list_modules()를 구현해야 하는 객체가 임포터인지, 로더인지, 아니면 둘 다인지는 다소 불분명합니다.

이 PEP는 대체 위치에서 모듈을 로드하는 쪽으로 치우쳐 있습니다: 현재로서는 대체 파일 형식이나 대체 컴파일러로 모듈을 로드하기 위한 전용 솔루션을 제공하지 않습니다. 반면, 표준 라이브러리의 ihooks 모듈은 이를 수행하는 상당히 간단한 방법을 실제로 가지고 있습니다. Quixote 프로젝트 [7]는 이 기법을 사용하여 PTL 파일을 마치 일반 Python 모듈인 것처럼 임포트합니다. 새로운 훅으로 동일한 작업을 수행하려면 ihooks의 부분집합을 구현하는 새로운 모듈을 새 방식 임포터로 추가하거나, 훅을 걸 수 있는 내장 경로 임포터 객체를 추가해야 할 것입니다.

이 PEP 내에는 훅을 “쌓는” 것에 대한 구체적인 지원이 없습니다. 예를 들어, .tar.gz 파일에서 모듈을 로드하는 별도의 훅들을 조합하여 tar.gz 파일에서 모듈을 로드하는 훅을 작성하는 방법은 명확하지 않습니다. 하지만 기존 훅 메커니즘(기본적인 “__import__ 교체” 방식이든, 기존의 임포트 훅 모듈들 중 하나이든)에는 이러한 스태킹에 대한 지원이 없으므로, 이 기능은 새 메커니즘의 명백한 요구사항은 아닙니다. 다만 향후 개선 사항으로 고려해 볼 가치는 있을 것입니다.

sys.path가 처리되기 전에 실행되는 훅을 (sys.meta_path를 통해) 추가하는 것은 가능합니다. 하지만 sys.path가 처리된 후에 실행되는 훅을 추가하는 동등한 방법은 없습니다. 현재로서는 sys.path가 처리된 후에 훅이 필요한 경우, sys.path 끝에 임의의 “쿠키” 문자열을 추가하고, 일반적인 sys.path_hooks 처리 과정을 통해 필요한 훅을 이 쿠키와 연결함으로써 이를 흉내 낼 수 있습니다. 장기적으로는 경로 처리 코드가 sys.meta_path상의 “진짜” 훅이 될 것이며, 그 단계에 이르면 사용자 정의 훅을 그 전이나 후에 삽입하는 것이 가능해질 것입니다.

구현

2.3a1부터 PEP 302의 구현이 파이썬에 통합되었습니다. 이전 버전은 패치 #652586 [9]로 제공되며, 더 흥미로운 점은 해당 이슈에 개발 및 설계에 대한 꽤 상세한 이력이 담겨 있다는 것입니다.

PEP 273PEP 302의 임포트 훅을 사용하여 구현되었습니다.

참고 문헌 및 각주