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

Python 개선 제안 한국어 번역

PEP 451 – 임포트 시스템을 위한 ModuleSpec 타입

Author:
Eric Snow <ericsnowcurrently at gmail.com>
BDFL-Delegate:
Brett Cannon <brett at python.org>, Alyssa Coghlan <ncoghlan at gmail.com>
Discussions-To:
Import-SIG list
Status:
Final
Type:
Standards Track
Created:
08-Aug-2013
Python-Version:
3.4
Post-History:
08-Aug-2013, 28-Aug-2013, 18-Sep-2013, 24-Sep-2013, 04-Oct-2013
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

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

초록

이 PEP에서는 importlib.machinery에 “ModuleSpec”이라는 새 클래스를 추가할 것을 제안합니다. 이 클래스는 모듈을 로드하는 데 사용되는 모든 임포트 관련 정보를 제공하며, 모듈을 먼저 로드하지 않아도 사용할 수 있습니다. 파인더는 로더 대신 모듈의 스펙을 직접 제공하게 됩니다(로더는 계속 간접적으로 제공하게 됩니다). 임포트 메커니즘은 모듈 스펙을 활용하도록 조정되며, 여기에는 모듈 스펙을 사용하여 모듈을 로드하는 기능도 포함됩니다.

용어와 개념

이 제안의 변경 사항은 현재 (안타깝게도) 모호한 기존 용어와 개념 몇 가지를 더 명확하게 만들 기회를 제공합니다. 이 제안에서는 새로운 개념도 소개합니다. 마지막으로, 사람들이 그다지 익숙하지 않을 수 있는 기존 용어 몇 가지도 설명할 가치가 있습니다. 맥락을 위해, 다음은 세 용어 및 개념 그룹 모두에 대한 간략한 요약입니다. 임포트 시스템에 대한 더 자세한 설명은 [2]에서 확인할 수 있습니다.

이름

이 제안에서 모듈의 “이름”은 완전 수식 이름을 의미하며, 모듈의 부모(있는 경우)의 완전 수식 이름에 모듈의 단순 이름을 마침표로 결합한 것입니다.

파인더

“파인더”는 임포트 시스템이 모듈을 로드하는 데 사용해야 하는 로더를 식별하는 객체입니다. 현재는 파인더의 find_module() 메서드를 호출하여 이를 수행하며, 이 메서드는 로더를 반환합니다.

파인더는 로더를 제공하는 일만 전적으로 담당하며, find_module() 메서드를 통해 로더를 제공합니다. 그러면 임포트 시스템은 해당 로더를 사용하여 모듈을 로드합니다.

로더

“로더”는 임포트 중에 모듈을 로드하는 데 사용되는 객체입니다. 현재는 로더의 load_module() 메서드를 호출하여 이를 수행합니다. 로더는 로드할 수 있는 모듈에 대한 정보와 그러한 모듈과 연결된 소스의 데이터에 대한 정보를 가져오는 API도 제공할 수 있습니다.

현재 로더는 load_module()을 통해 특정 상용구 임포트 관련 작업을 담당합니다. 이러한 작업은 다음과 같습니다.

  1. 일부(모듈 관련) 검증을 수행합니다.
  2. 모듈 객체를 생성합니다.
  3. 모듈에 임포트 관련 특성을 설정합니다.
  4. 모듈을 sys.modules에 “등록”합니다.
  5. 모듈을 실행합니다.
  6. 모듈을 로드하는 동안 오류가 발생한 경우 정리합니다.

이 모든 작업은 임포트 시스템이 Loader.load_module()을 호출하는 동안 수행됩니다.

출처

이는 새로운 용어이자 개념입니다. 이 개념은 이미 임포트 시스템에 미묘하게 존재하지만, 이 제안에서는 그 개념을 명시적으로 만듭니다.

가져오기 컨텍스트에서 “origin”은 모듈이 비롯된 시스템(또는 시스템 내 리소스)을 의미합니다. 이 제안의 목적상 “origin”은 그러한 리소스나 시스템을 식별하는 문자열이기도 합니다. “origin”은 모든 모듈에 적용됩니다.

예를 들어, 내장 모듈과 동결 모듈의 출처는 인터프리터 자체입니다. 가져오기 시스템은 이미 이 origin을 각각 “built-in”과 “frozen”으로 식별합니다. 이는 다음 모듈 repr에 나타나 있습니다: “<module ‘sys’ (built-in)>”.

실제로 모듈 repr은 이미 모듈의 출처를 나타내는, 암묵적이기는 하지만 비교적 신뢰할 수 있는 지표입니다. 다른 모듈도 “location” 항목에 설명된 대로 다른 수단을 통해 origin을 나타냅니다.

모듈의 출처를 해석하고 사용할지 여부와 그 방법은 전적으로 로더가 결정합니다.

위치

이는 새로운 용어입니다. 그러나 이 개념은 모듈의 __file____path__ 특성과 연결된 형태로, 그리고 다른 곳에서 사용되는 “path”라는 이름/용어와 관련하여 이미 임포트 시스템에 명확히 존재합니다.

“위치”는 전체 시스템이라기보다는 모듈이 로드되는 리소스 또는 “장소”입니다. 이는 “출처”에 해당합니다. 위치의 예로는 파일 시스템 경로와 URL이 있습니다. 위치는 리소스의 이름으로 식별되지만, 해당 리소스가 속한 시스템까지 반드시 식별하는 것은 아닙니다. 그러한 경우에는 로더가 시스템 자체를 식별해야 합니다.

다른 종류의 모듈 출처와 달리, 로더는 모듈 이름만으로 위치를 추론할 수 없습니다. 대신 로더에는 위치를 식별하는 문자열이 제공되어야 하며, 일반적으로 이는 로더를 생성하는 파인더가 제공합니다. 그러면 로더는 이 정보를 사용하여 모듈을 로드할 리소스의 위치를 찾습니다. 이론적으로는 주어진 위치의 모듈을 다양한 이름으로 로드할 수 있습니다.

임포트 시스템에서 위치의 가장 일반적인 예는 소스 모듈과 확장 모듈을 로드하는 파일입니다. 이러한 모듈의 경우 위치는 __file__ 특성의 문자열로 식별됩니다. 일부 모듈(예: 압축된 모듈)에서는 __file__이 특히 정확하지 않지만, 현재 임포트 시스템이 모듈에 위치가 있음을 나타내는 유일한 방법입니다.

위치가 있는 모듈을 “locatable”이라고 부를 수 있습니다.

캐시

가져오기 시스템은 최적화를 위해 컴파일된 모듈을 __pycache__ 디렉터리에 저장합니다. 오늘날 사용하는 이 모듈 캐시는 PEP 3147에 의해 제공되었습니다. 이 제안에서 모듈 캐싱과 관련된 API는 모듈의 __cache__ 속성과 importlib.util의 cache_from_source() 함수입니다. 로더는 모듈을 캐시에 저장하고 캐시에서 불러오는 역할을 담당합니다. 현재 캐시는 컴파일된 소스 모듈에만 사용됩니다. 그러나 로더는 다른 종류의 모듈에도 모듈 캐시를 활용할 수 있습니다.

패키지

개념도 용어도 변경되지 않습니다. 그러나 모듈과 패키지의 구분은 대부분 피상적입니다. 패키지는 모듈입니다. 패키지는 단순히 __path__ 속성을 가지며, 가져오기는 서브모듈에 바인딩된 속성을 추가할 수 있습니다. 일반적으로 인식되는 차이가 혼란의 원인이 됩니다. 이 제안은 그렇게 하는 것이 타당한 경우 패키지와 모듈의 구분을 명시적으로 덜 강조합니다.

동기

가져오기 시스템은 Python의 역사와 함께 발전해 왔습니다. 2002년 말에 PEP 302는 파인더와 로더 및 sys.meta_path를 통한 표준화된 가져오기 훅을 도입했습니다. Python 3.1과 함께 도입된 importlib 모듈은 이제 PEP 302에 설명된 API와 전체 가져오기 시스템의 순수 Python 구현을 공개합니다. 이제 가져오기 시스템을 이해하고 확장하기가 훨씬 쉬워졌습니다. 이는 Python 커뮤니티에 이점이지만, 이러한 향상된 접근성은 과제도 제시합니다.

더 많은 개발자가 가져오기 시스템을 이해하고 사용자 지정하게 될수록 파인더 및 로더 API의 모든 약점이 더 큰 영향을 미치게 됩니다. 따라서 이러한 약점을 가져오기 시스템에서 해결할 수 있는 시점이 빠를수록 좋으며, 이 제안으로 해결하고자 하는 약점이 몇 가지 있습니다.

첫째, 가져오기 시스템이 모듈에 관한 정보를 저장해야 할 때마다 일반적으로 가져오기 시스템에만 의미가 있는 속성이 모듈 객체에 더 많이 생깁니다. 향후 가져오기 관련 정보를 저장하고 가져오기 시스템 내부에서 전달할 수 있는 모듈별 네임스페이스가 있으면 좋을 것입니다. 둘째, 파인더와 로더 사이에는 API 공백이 있어 이를 마주할 때 불필요한 복잡성이 발생합니다. 관련 PEP 420 (네임스페이스 패키지) 구현에서는 이를 우회해야 했습니다. 별도의 제안에 관한 최근 작업 중에 이러한 복잡성이 다시 드러났습니다. [1]

위의 finderloader 섹션에서는 양자의 현재 책임을 자세히 설명합니다. 특히 로더는 다른 메서드를 통해 자신의 load_module() 메서드 기능을 제공할 필요가 없습니다. 따라서 모듈을 로드하지 않아도 모듈에 관한 가져오기 관련 정보를 이용할 수 있을 가능성이 있지만, 그 외에는 해당 정보가 노출되지 않습니다.

또한 load_module()과 관련된 요구 사항은 모든 로더에 공통이며 대부분 정확히 같은 방식으로 구현됩니다. 이는 모든 로더가 동일한 보일러플레이트 코드를 중복해야 한다는 의미입니다. importlib.util은 이를 지원하는 몇 가지 도구를 제공하지만, 가져오기 시스템이 이러한 책임을 단순히 맡는다면 더 유용할 것입니다. 문제는 이렇게 하면 load_module()이 쉽게 계속 지원할 수 있는 사용자 지정의 범위가 제한된다는 것입니다.

더 중요하게는, 파인더는 로더의 load_module()이 필요로 할 정보를 제공할 수도 있지만, 현재 이를 로더에 전달할 일관된 방법이 없습니다. 이는 이 제안이 해결하려는 파인더와 로더 사이의 간극입니다.

마지막으로, 임포트 시스템이 파인더의 find_module()을 호출하면 파인더는 메서드의 컨텍스트 밖에서도 유용한 모듈 관련 다양한 정보를 사용합니다. 현재는 해당 메서드 호출 이후에도 모듈별 정보를 유지할 수 있는 선택지가 제한적입니다. find_module()은 로더만 반환하기 때문입니다. 이러한 제약에 대한 일반적인 선택지는 파인더 자체의 어딘가에 모듈-정보 매핑으로 정보를 저장하거나, 로더에 저장하는 것입니다.

안타깝게도 로더는 모듈별일 필요가 없습니다. 게다가 파인더가 제공할 수 있는 유용한 정보 중 일부는 모든 파인더에 공통적이므로, 이상적으로는 임포트 시스템이 이러한 세부 사항을 처리할 수 있어야 합니다. 이는 앞서 설명한 파인더와 로더 사이의 동일한 간극입니다.

이 결함으로 인해 발생하는 복잡성의 한 예로, Python 3.3의 네임스페이스 패키지 구현은 find_module()이 네임스페이스 검색 위치를 제공할 좋은 방법이 없었기 때문에 FileFinder.find_loader()를 추가했습니다(PEP 420 참조).

이 간극에 대한 해답은 모듈별 정보를 포함하고 모듈 로딩과 관련된 상용구 기능을 처리하는 ModuleSpec 객체입니다.

사양

목표는 파인더와 로더의 의미를 가능한 한 적게 변경하면서 둘 사이의 간극을 해결하는 것입니다. 일부 기능과 정보가 새로운 ModuleSpec 형식으로 이동하더라도 동작은 동일하게 유지되어야 합니다. 그러나 명확성을 위해 파인더와 로더의 의미를 명시적으로 식별합니다.

다음은 이 PEP에서 설명하는 변경 사항에 대한 상위 수준의 요약입니다. 자세한 내용은 뒤의 절에서 확인할 수 있습니다.

importlib.machinery.ModuleSpec (새 항목)

임포트 중 모듈의 임포트 시스템 관련 상태를 캡슐화합니다. 자세한 설명은 아래의 ModuleSpec 절을 참조하십시오.

  • ModuleSpec(name, loader, *, origin=None, loader_state=None, is_package=None)

특성:

  • name - 모듈의 정규화된 이름을 나타내는 문자열입니다.
  • loader - 로딩에 사용할 로더입니다.
  • origin - 모듈이 로드되는 위치의 이름입니다. 예를 들어 내장 모듈의 경우 “builtin”이고 소스에서 로드된 모듈의 경우 파일 이름입니다.
  • submodule_search_locations - 패키지인 경우 서브모듈을 찾을 위치를 나타내는 문자열의 목록입니다(그 외에는 None).
  • loader_state - 로딩 중 사용할 추가 모듈별 데이터를 담는 컨테이너입니다.
  • cached (property) - 컴파일된 모듈을 저장할 위치를 나타내는 문자열입니다.
  • parent (RO-property) - 모듈이 서브모듈로 속한 패키지의 정규화된 이름입니다(또는 None).
  • has_location (RO-property) - 모듈의 “origin” 특성이 위치를 가리키는지 여부를 나타내는 플래그입니다.

importlib.util 추가 사항

이는 파인더의 편의를 위해 제공되는 ModuleSpec 팩토리 함수입니다. 자세한 내용은 아래의 Factory Functions 절을 참조하십시오.

  • spec_from_file_location(name, location, *, loader=None, submodule_search_locations=None) - 파일 중심 정보와 로더 API를 바탕으로 명세를 작성합니다.
  • spec_from_loader(name, loader, *, origin=None, is_package=None) - 로더를 사용하여 누락된 정보를 채운 명세를 작성합니다. API입니다.

기타 API 추가 사항

  • importlib.find_spec(name, path=None, target=None)은 importlib.find_loader()를 대체하며 이와 정확히 동일하게 작동하지만, 로더 대신 명세를 반환합니다.

파인더의 경우:

  • importlib.abc.MetaPathFinder.find_spec(name, path, target)와 importlib.abc.PathEntryFinder.find_spec(name, target)는 가져오기에 사용할 모듈 명세를 반환합니다.

로더의 경우:

  • importlib.abc.Loader.exec_module(module)은 자체 네임스페이스에서 모듈을 실행합니다. 이는 importlib.abc.Loader.load_module()을 대체하며, 모듈 실행 기능을 담당합니다.
  • importlib.abc.Loader.create_module(spec)(선택 사항)는 로딩에 사용할 모듈을 반환합니다.

모듈의 경우:

  • 모듈 객체에는 __spec__라는 새로운 속성이 추가됩니다.

API 변경 사항

  • InspectLoader.is_package()는 선택 사항이 됩니다.

사용 중단 예정

  • importlib.abc.MetaPathFinder.find_module()
  • importlib.abc.PathEntryFinder.find_module()
  • importlib.abc.PathEntryFinder.find_loader()
  • importlib.abc.Loader.load_module()
  • importlib.abc.Loader.module_repr()
  • importlib.util.set_package()
  • importlib.util.set_loader()
  • importlib.find_loader()

제거 사항

이들은 Python 3.4가 릴리스되기 전에 도입되었으므로 간단히 제거할 수 있습니다.

  • importlib.abc.Loader.init_module_attrs()
  • importlib.util.module_to_load()

기타 변경 사항

  • importlib의 가져오기 시스템 구현은 ModuleSpec을 사용하도록 변경됩니다.
  • importlib.reload()은 ModuleSpec을 사용합니다.
  • 모듈의 import 관련 속성(__spec__이외)은 해당 모듈을 import하는 동안 import 시스템에서 더 이상 직접 사용되지 않습니다. 그러나 다른 모듈(예: 서브모듈)을 로드할 때 이러한 속성(예: __path__)을 사용하는 데에는 영향을 주지 않습니다.
  • import 관련 속성은 import 시스템을 제외하고는 더 이상 모듈에 직접 추가해서는 안 됩니다.
  • 모듈 타입의 __repr__()은 ModuleSpec을 활용하는 순수 Python 구현을 감싸는 얇은 래퍼가 됩니다.
  • __main__ 모듈의 스펙에는 적절한 이름과 출처가 반영됩니다.

하위 호환성

  • 파인더가 find_spec()을 정의하지 않으면 find_module()이 반환한 로더에서 스펙을 파생합니다.
  • PathEntryFinder.find_loader()가 여전히 find_module()보다 우선합니다.
  • exec_module()이 정의되지 않은 경우 Loader.load_module()을 사용합니다.

변경되지 않는 사항

  • import 문의 구문과 의미 체계입니다.
  • 기존 파인더와 로더는 계속 정상적으로 작동합니다.
  • import 관련 모듈 속성은 계속 동일한 정보로 초기화됩니다.
  • 파인더는 계속 로더를 생성합니다(이제 스펙에 저장합니다).
  • 모듈이 Loader.load_module()을 정의하는 경우, 동일한 모든 요구 사항이 적용되며 여전히 직접 호출할 수 있습니다.
  • 로더는 계속 모듈 데이터 API를 담당합니다.
  • importlib.reload()은 계속 import 관련 속성을 덮어씁니다.

책임

이 PEP 이후 책임이 어디에 있는지 간단히 설명합니다.

파인더:

  • 모듈을 로드할 수 있는 로더를 생성하거나 식별합니다.
  • 모듈의 스펙을 생성합니다.

로더:

  • 모듈을 생성합니다(선택 사항).
  • 모듈을 실행합니다.

ModuleSpec:

  • 모듈 로딩을 조정합니다.
  • sys.modules 관리 및 import 관련 속성 설정을 포함한 모듈 로딩용 보일러플레이트를 제공합니다.
  • 로더가 모듈을 생성하지 않는 경우 모듈을 생성합니다.
  • 실행할 모듈을 전달하여 loader.exec_module()을 호출합니다.
  • 로더가 모듈을 실행하는 데 필요한 모든 정보를 포함합니다.
  • 모듈의 repr을 제공하십시오.

기존 파인더와 로더는 어떻게 다르게 해야 합니까?

즉시 말입니까? 아무것도 없습니다. 현재 상태는 사용 중단될 예정이지만 계속 작동합니다. 그러나 이 PEP와 관련하여 파인더와 로더의 작성자가 변경해야 할 사항은 다음과 같습니다.

  • 파인더에 find_spec()을 구현하십시오.
  • 가능하다면 로더에 exec_module()을 구현하십시오.

importlib.util의 ModuleSpec 팩터리 함수는 기존 파인더를 변환하는 데 도움이 되도록 설계되었습니다. spec_from_loader()와 spec_from_file_location()은 이와 관련하여 모두 간단한 유틸리티입니다.

기존 로더에서 exec_module()은 load_module()의 보일러플레이트가 아닌 부분을 비교적 직접적으로 변환한 것이어야 합니다. 드문 경우에는 로더가 create_module()도 구현해야 합니다.

ModuleSpec 사용자

ModuleSpec 객체에는 서로 다른 대상 독자가 3개 있습니다. Python 자체, 임포트 훅, 일반 Python 사용자입니다.

Python은 임포트 메커니즘, 인터프리터 시작 과정 및 다양한 표준 라이브러리 모듈에서 스펙을 사용합니다. pkgutil처럼 임포트 지향적인 모듈도 있고, pickle 및 pydoc처럼 그렇지 않은 모듈도 있습니다. 모든 경우에 전체 ModuleSpec API가 사용됩니다.

임포트 훅(파인더와 로더)은 특정한 방식으로 스펙을 사용합니다. 우선 파인더는 importlib.util의 스펙 팩터리 함수를 사용하여 스펙 객체를 만들 수 있습니다. 또한 스펙이 생성된 후 스펙 속성을 직접 조정할 수도 있습니다. 둘째로, 파인더는 로더가 모듈 생성 또는 실행 중에 사용할 추가 정보를 스펙에 바인딩할 수 있습니다(finder_extras에서). 마지막으로 로더는 모듈을 생성하거나 실행할 때 스펙의 속성을 사용합니다.

Python 사용자는 모듈의 __spec__을 검사하여 해당 객체에 관한 임포트 관련 정보를 확인할 수 있습니다. 일반적으로 Python 애플리케이션과 대화형 사용자는 ModuleSpec 팩터리 함수나 인스턴스 메서드를 사용하지 않습니다.

로딩은 어떻게 작동합니까?

다음은 모듈의 스펙과 새로운 로더 API를 활용하도록 조정한, 로딩 중 임포트 메커니즘의 작업 개요입니다.:

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None and spec.submodule_search_locations is not None:
    # Namespace package
    sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
    spec.loader.load_module(spec.name)
    # __loader__ and __package__ would be explicitly set here for
    # backwards-compatibility.
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
module_to_return = sys.modules[spec.name]

이러한 단계는 Loader.load_module()이 이미 수행할 것으로 예상되는 작업과 정확히 같습니다. 따라서 로더는 exec_module()만 구현하면 되므로 단순해집니다.

sys.modules에서 모듈을 반환해야 한다는 점에 유의하십시오. 로딩 중에 모듈이 sys.modules에서 자신을 교체했을 수 있습니다. 이 사용 사례를 수용할 사후 임포트 훅 API가 없으므로 이를 처리해야 합니다. 그러나 교체가 발생한 경우에는 객체에 임포트 관련 모듈 속성을 설정하는 것에 대해 걱정하지 않습니다. 이렇게 하는 경우 모듈 작성자가 직접 처리해야 합니다.

다시 로드하는 방식

reload()에 해당하는 개요입니다.:

_RELOADING = {}

def reload(module):
    try:
        name = module.__spec__.name
    except AttributeError:
        name = module.__name__
    spec = find_spec(name, target=module)

    if sys.modules.get(name) is not module:
        raise ImportError
    if spec in _RELOADING:
        return _RELOADING[name]
    _RELOADING[name] = module
    try:
        if spec.loader is None:
            # Namespace loader
            _init_module_attrs(spec, module)
            return module
        if spec.parent and spec.parent not in sys.modules:
            raise ImportError

        _init_module_attrs(spec, module)
        # Ignoring backwards-compatibility call to load_module()
        # for simplicity.
        spec.loader.exec_module(module)
        return sys.modules[name]
    finally:
        del _RELOADING[name]

여기서 핵심은 Loader.exec_module()로 전환하면 로더가 실행 시점에 이것이 다시 로드인지 아닌지 쉽게 알 수 없게 된다는 점입니다. 이 제안 이전에는 모듈이 이미 sys.modules에 있는지 간단히 확인할 수 있었습니다. 이제 로드 중(다시 로드 중이 아님)에 exec_module()이 호출될 때쯤이면 가져오기 메커니즘이 이미 모듈을 sys.modules에 배치했을 것입니다. 이것이 find_spec()에 the “target” parameter가 있는 이유 중 하나입니다.

reload의 의미 체계는 현재와 본질적으로 동일하게 유지됩니다 [5]. 이 PEP가 일부 종류의 지연 로딩 모듈에 미치는 영향은 논의의 대상이었습니다. [4]

ModuleSpec

특성

다음 각 이름은 ModuleSpec 객체의 특성입니다. None 값은 “설정되지 않음”을 나타냅니다. 이는 특성이 단순히 존재하지 않는 모듈 객체의 경우와 대조됩니다. 대부분의 특성은 모듈의 가져오기 관련 특성에 대응합니다. 다음은 그 매핑입니다. 이 매핑의 역매핑은 exec_module()을 호출하기 직전에 가져오기 메커니즘이 모듈 특성을 설정하는 방식을 설명합니다.

ModuleSpec에서 모듈에서
name __name__
loader __loader__
parent __package__
origin __file__*
cached __cached__*,**
submodule_search_locations __path__**
loader_state -
has_location -
* Set on the module only if spec.has_location is true.
** Set on the module only if the spec attribute is not None.

parent 및 has_location은 읽기 전용 속성이지만, 나머지 속성은 모듈 사양이 생성된 후, 심지어 가져오기가 완료된 후에도 바꿀 수 있습니다. 이를 통해 사양을 직접 수정하는 것이 가장 좋은 선택인 특수한 경우를 처리할 수 있습니다. 그러나 일반적인 사용에서는 모듈 사양의 상태를 변경하지 않아야 합니다.

origin

“origin”은 모듈이 비롯된 장소의 이름을 나타내는 문자열입니다. 위의 origin을 참조하십시오. 정보 제공 목적 외에도 모듈의 repr에서 사용됩니다. “has_location”이 참인 사양의 경우 __file__은 “origin”의 값으로 설정됩니다. 내장 모듈의 경우 “origin”은 “built-in”으로 설정됩니다.

has_location

위의 location 섹션에서 설명했듯이, 많은 모듈은 “위치 지정 가능”합니다. 즉, 모듈을 로드할 해당 리소스가 존재하며 그 리소스를 문자열로 설명할 수 있습니다. 반면 위치 지정이 불가능한 모듈은 이러한 방식으로 로드할 수 없습니다. 예를 들어 내장 모듈과 코드에서 동적으로 생성된 모듈이 이에 해당합니다. 이러한 모듈은 이름으로만 접근할 수 있으므로 “origin”은 있지만 “location”은 없습니다.

모듈을 위치 지정할 수 있으면 “has_location”은 참입니다. 이 경우 사양의 origin이 위치로 사용되고 __file__은 spec.origin으로 설정됩니다. 추가 위치 정보가 필요한 경우(예: zipimport), 해당 정보는 spec.loader_state에 저장될 수 있습니다.

로더에 load_data() 메서드가 존재하면 “has_location”이 암시될 수 있습니다.

참고로 위치 지정 가능한 모든 모듈을 캐시할 수 있는 것은 아니지만, 대부분은 캐시할 수 있습니다.

submodule_search_locations

서브모듈을 검색할 위치 문자열의 목록으로, 일반적으로 디렉터리 경로가 포함됩니다. 모듈이 패키지이면 이 값은 목록으로 설정됩니다(빈 목록일 수도 있습니다). 그렇지 않으면 None입니다.

해당 모듈 속성의 이름인 __path__은 비교적 모호합니다. 이를 그대로 반영하는 대신 목적을 명확하게 보여 주는 더 명시적인 속성 이름을 사용합니다.

loader_state

파인더는 로더가 로드 중 사용할 추가 데이터를 제공하기 위해 loader_state를 임의의 값으로 설정할 수 있습니다. None 값은 기본값이며 추가 데이터가 없음을 나타냅니다. 그 외에는 관련 추가 정보가 포함된 dict, list 또는 types.SimpleNamespace와 같은 임의의 객체로 설정할 수 있습니다.

예를 들어 zipimporter는 origin에서 압축 보관 파일 이름을 추출하거나 각 찾기 작업마다 사용자 지정 로더를 생성할 필요 없이, 이를 사용하여 압축 보관 파일 이름을 로더에 직접 전달할 수 있습니다.

loader_state는 파인더와 해당 로더가 사용하기 위한 것입니다. 다른 용도를 위한 안정적인 리소스라는 보장은 없습니다.

팩토리 함수

spec_from_file_location(name, location, *, loader=None, submodule_search_locations=None)

파일 중심 정보와 로더 API를 바탕으로 명세를 빌드합니다.

  • “origin”은 location으로 설정됩니다.
  • “has_location”은 True로 설정됩니다.
  • “cached”는 cache_from_source()를 호출한 결과로 설정됩니다.
  • “origin”은 loader.get_filename()에서 추론할 수 있습니다(location이 전달되지 않은 경우).
  • location이 파일 이름이면 “loader”는 접미사에서 추론할 수 있습니다.
  • location이 파일 이름이면 “submodule_search_locations”는 loader.is_package()와 os.path.dirname(location)에서 추론할 수 있습니다.

spec_from_loader(name, loader, *, origin=None, is_package=None)

로더 API를 사용하여 누락된 정보를 채운 명세를 빌드합니다.

  • “has_location”은 loader.get_data에서 추론할 수 있습니다.
  • “origin”은 loader.get_filename()에서 추론할 수 있습니다.
  • location이 파일 이름이면 “submodule_search_locations”는 loader.is_package()와 os.path.dirname(location)에서 추론할 수 있습니다.

하위 호환성

ModuleSpec에는 그런 것이 없습니다. Finder.find_module()이 로더 대신 모듈 명세를 반환하도록 한다면 이야기가 달라집니다. 그런 경우 명세는 대신 반환되었을 로더처럼 동작해야 합니다. 그렇게 하는 것은 비교적 간단하지만 불필요한 복잡성을 초래합니다. 이는 이 PEP의 이전 버전에 포함되어 있었습니다.

서브클래싱

ModuleSpec의 서브클래스는 허용되지만 필요하지는 않습니다. 단순히 loader_state를 설정하거나 사용자 지정 파인더 또는 로더에 기능을 추가하는 편이 더 적합할 가능성이 높으므로 먼저 시도해야 합니다. 그러나 서브클래스가 여전히 임포트 시스템의 요구 사항을 충족하는 한, 해당 유형의 객체는 Finder.find_spec()의 반환값으로 완전히 적합합니다. 덕 타이핑에도 동일한 사항이 적용됩니다.

기존 유형

모듈 객체

__spec__을 추가하는 것 외에는 임포트와 관련된 모듈 속성이 변경되거나 폐기되지 않지만, 일부 속성은 변경되거나 폐기될 수 있습니다. 그러한 폐기는 Python 4까지 기다릴 수 있습니다.

모듈의 명세는 이에 대응하는 임포트 관련 속성과 동기화된 상태로 유지되지 않습니다. 서로 다를 수 있지만 실제로는 일반적으로 동일합니다.

한 가지 주목할 만한 예외는 -m 플래그를 사용하여 모듈을 스크립트로 실행하는 경우입니다. 이 경우 module.__spec__.name은 실제 모듈 이름을 반영하는 반면 module.__name____main__이 됩니다.

동일한 이름을 가진 두 모듈 사이에서 모듈의 spec이 동일하다고 보장되지는 않습니다. 마찬가지로 importlib.find_spec()를 연속해서 호출해도 동일한 객체나 심지어 동등한 객체가 반환된다고 보장할 수는 없지만, 적어도 후자일 가능성은 높습니다.

파인더

파인더는 모듈을 로드하는 데 사용해야 할 로더를 식별하고, 일반적으로 생성하는 역할을 여전히 담당합니다. 이제 해당 로더는 직접 반환되는 대신 find_spec()이 반환하는 모듈 spec에 저장됩니다. PEP가 없는 현재의 경우와 마찬가지로, 로더를 생성하는 데 비용이 많이 든다면 해당 비용을 나중으로 미루도록 로더를 설계할 수 있습니다.

MetaPathFinder.find_spec(name, path=None, target=None)

PathEntryFinder.find_spec(name, target=None)

find_spec()이 호출되면 파인더는 ModuleSpec 객체를 반환해야 합니다. 이 새로운 메서드는 find_module()과 find_loader()를 대체합니다(PathEntryFinder의 경우). 로더에 find_spec()이 없으면 하위 호환성을 위해 find_module()과 find_loader()가 대신 사용됩니다.

로더에 또 하나의 유사한 메서드를 추가하는 것은 실용성을 고려한 조치입니다. find_module()을 로더 대신 spec을 반환하도록 변경할 수도 있습니다. 특히 PathEntryFinder.find_loader()가 Python 3.3에서 막 추가되었다는 점을 고려하면, import API가 충분히 많은 어려움을 겪어 왔기 때문에 이는 매력적으로 보입니다. 그러나 추가적인 복잡성과 명확성이 부족한 메서드 이름은 그럴 만한 가치가 없습니다.

“target” 매개변수와 find_spec()

find_spec() 호출에는 선택적으로 “target” 인자가 포함될 수 있습니다. 이는 이후 로딩의 대상으로 사용될 모듈 객체입니다. 일반적인 임포트 중에는(기본값에서도) “target”이 None이며, 이는 대상 모듈이 아직 생성되지 않았음을 의미합니다. 다시 로드하는 동안에는 reload()에 전달된 모듈이 target으로서 find_spec()에 전달됩니다. 이 인자를 사용하면 파인더가 다른 방법으로는 얻을 수 없는 더 많은 정보를 바탕으로 모듈 spec을 구성할 수 있습니다. 이는 사용할 로더를 식별할 때 특히 중요합니다.

find_spec()을 통해 파인더는 spec에서 반환할 로더를 항상 식별하거나 None을 반환합니다. 로더가 식별되는 시점에 파인더는 “target”이 전달된 경우 해당 로더가 대상 모듈로 로드하는 것을 지원하는지도 결정해야 합니다. 이 결정을 위해 로더에 문의해야 할 수도 있습니다.

파인더가 해당 로더가 대상 모듈로 로드하는 것을 지원하지 않는다고 판단하면, 다른 로더를 찾거나 ImportError를 발생시켜야 합니다(모듈 임포트를 완전히 중단합니다). 이 판단은 특히 다시 로드하는 동안 중요합니다. How Reloading Will Work에서 설명했듯이 로더는 더 이상 자체적으로 다시 로드 상황을 간단히 식별할 수 없기 때문입니다.

“target” 매개변수에 대한 두 가지 대안이 제시되었습니다. find_spec() 대신 Loader.supports_reload()를 사용하거나 Loader.exec_module()에 “target”을 추가하는 것입니다. supports_reload()는 다시 로드 상황에 대한 초기 접근 방식이었습니다. [6] 그러나 로더별이며 다시 로드에 편중된 접근 방식에 대해서는 일부 반대가 있었습니다. [7]

exec_module()의 “target”에 관해서는, 다시 로드하는 동안 로더가 단순히 “이 로더가 이 모듈의 다시 로드를 지원하는가”를 확인하는 것보다 대상 모듈(또는 spec)에서 더 많은 정보를 필요로 할 수 있으며, 이러한 정보는 load_module()에서 벗어나면서 더 이상 사용할 수 없게 됩니다. 제안된 방안 중 하나는 exec_module()에 “target”과 유사한 것을 추가하는 것이었습니다. [8] 그러나 대신 find_spec()에 “target”을 배치하는 것이 이 PEP의 목표에 더 부합합니다. 또한 이렇게 하면 supports_reload()가 필요하지 않게 됩니다.

네임스페이스 패키지

현재 경로 엔트리 파인더는 가능한 네임스페이스 패키지의 일부를 찾았음을 나타내기 위해 find_loader()에서 (None, portions)을 반환할 수 있습니다. 동일한 효과를 얻으려면 find_spec()은 “loader”가 None(즉, 설정되지 않음)으로 설정되고, submodule_search_locations가 find_loader()에서 제공되었을 동일한 portions로 설정된 스펙을 반환해야 합니다. 이러한 스펙을 처리하는 방법은 PathFinder에 달려 있습니다.

로더

Loader.exec_module(module)

로더에는 새로운 메서드인 exec_module()이 추가됩니다. 이 메서드의 유일한 작업은 모듈을 “exec”하여 결과적으로 모듈의 네임스페이스를 채우는 것입니다. 모듈 객체를 생성하거나 준비하는 일과 그 이후의 정리를 담당하지는 않습니다. 반환값은 없습니다. exec_module()은 로딩과 다시 로딩 모두에서 사용됩니다.

exec_module()은 두 번 이상 호출되는 경우를 적절히 처리해야 합니다. 일부 종류의 모듈에서는 메서드가 처음 호출된 이후 매번 ImportError를 발생시키는 것을 의미할 수 있습니다. 이는 일부 종류의 모듈이 제자리 다시 로딩을 지원하지 않는 다시 로딩에서 특히 중요합니다.

Loader.create_module(spec)

로더는 exec할 새 모듈을 반환하는 create_module()도 구현할 수 있습니다. 기본 모듈 생성 코드를 사용해야 함을 나타내기 위해 None을 반환할 수 있습니다. 일반적이지는 않지만 create_module()의 한 가지 사용 사례는 내장 모듈 타입의 서브클래스인 모듈을 제공하는 것입니다. 대부분의 로더는 create_module()을 구현할 필요가 없습니다,

create_module()은 동일한 스펙/모듈에 대해 두 번 이상 호출되는 경우를 적절히 처리해야 합니다. 여기에는 None을 반환하거나 ImportError를 발생시키는 것이 포함될 수 있습니다.

Note

exec_module()과 create_module()은 가져오기 관련 모듈 속성을 설정해서는 안 됩니다. load_module()이 그렇게 한다는 사실은 이 제안이 바로잡으려는 설계 결함입니다.

기타 변경 사항:

PEP 420에서는 모듈 타입의 __repr__()에서 특수 처리해야 하는 경우를 줄이기 위해 선택적 module_repr() 로더 메서드를 도입했습니다. 이 메서드는 ModuleSpec의 일부이므로 로더에서는 더 이상 사용되지 않도록 폐기될 예정입니다. 그러나 로더에 이 메서드가 존재하면 해당 메서드만 사용됩니다.

Python 3.4 릴리스 전에 추가된 Loader.init_module_attr() 메서드는 ModuleSpec의 동일한 메서드를 사용하도록 제거됩니다.

그러나 동일한 정보가 ModuleSpec에 있더라도 InspectLoader.is_package()는 폐기되지 않습니다. ModuleSpec은 해당 정보를 다른 방법으로 사용할 수 없는 경우 자체 is_package를 채우는 데 이를 사용할 수 있습니다. 그래도 선택 사항으로 만들어질 예정입니다.

로딩 중 모듈을 실행하는 것 외에도 로더는 모듈 관련 데이터에 관한 API를 제공할 직접적인 책임을 계속 집니다.

기타 변경 사항

  • importlib에서 제공하는 다양한 파인더와 로더는 이 제안을 준수하도록 업데이트됩니다.
  • 표준 라이브러리의 가져오기 관련 API(특히 finder와 loader)에 대한 다른 구현이나 종속성도 이 PEP에 맞게 마찬가지로 조정됩니다. 이러한 변경 사항은 계속 작동해야 하지만, 누락된 변경 사항은 Python 3.4.x 시리즈의 버그로 간주해야 합니다.
  • __main__ 모듈의 spec은 인터프리터가 시작된 방식을 반영합니다. 예를 들어 -m을 사용하면 spec의 이름은 사용된 모듈의 이름이 되지만, __main__.__name__은 여전히 “__main__”입니다.
  • 더 이상 사용되지 않게 되는 importlib.find_loader()를 본떠 importlib.find_spec()을 추가합니다.
  • importlib.reload()는 ModuleSpec을 사용하도록 변경됩니다.
  • 이제 importlib.reload()는 모듈별 가져오기 잠금을 사용합니다.

참조 구현

참조 구현은 http://bugs.python.org/issue18864 에서 이용할 수 있습니다.

구현 참고 사항

* 이 PEP의 구현은 pkgutil(및 setuptools)에 미치는 영향을 고려해야 합니다. pkgutil에는 PEP 302에 대한 일부 일반적인 함수 기반 확장이 있으며, importlib가 도구에서 이를 알지 못하는 상태로 loader를 래핑하기 시작하면 이러한 확장이 중단될 수 있습니다.

* 살펴보아야 할 다른 모듈로는 runpy(및 pythonrun.c), pickle, pydoc, inspect가 있습니다.

예를 들어 pickle은 __main__인 경우 module.__spec__.name을 확인하도록 업데이트해야 합니다.

PEP에 추가하는 내용 중 거부된 것

이 제안의 범위에 충분히 잘 맞지 않는 몇 가지 추가 내용이 제안되었습니다.

has_location, cached, submodule_search_locations를 분리하는 ModuleSpec의 “PathModuleSpec” 서브클래스는 없습니다. 그렇게 하면 분리가 더 깔끔해질 수 있지만, 모듈 객체에는 그러한 구분이 없습니다. ModuleSpec은 두 경우를 동등하게 지원합니다.

“ModuleSpec.is_package”는 간단한 추가 속성일 수 있지만(self.submodule_search_locations is not None의 별칭), 모듈과 패키지 사이의 인위적이고 (대부분 잘못된) 구분을 영속화합니다.

모듈 spec의 Factory Functions은 ModuleSpec의 클래스 메서드가 될 수 있습니다. 그러나 그러면 __spec__를 통해 모든 모듈에 이를 노출하게 되므로, 고급 사용자가 아닌 Python 사용자에게 불필요한 혼란을 줄 가능성이 있습니다. 팩토리 함수에는 finder 작성자를 지원한다는 특정 사용 사례가 있습니다. ModuleSpec Users를 참조하십시오.

마찬가지로 가져오기 메커니즘이 모듈 spec을 사용하는 구체적인 방식을 노출하는 몇 가지 다른 메서드를 ModuleSpec에 추가할 수 있습니다.

  • create() - Loader.create_module()을 감싸는 래퍼입니다.
  • exec(module) - Loader.exec_module()을 감싸는 래퍼입니다.
  • load() - 더 이상 사용되지 않는 Loader.load_module()에 해당하는 메서드입니다.

팩토리 함수와 마찬가지로, module.__spec__을 통해 이러한 메서드를 노출하는 것은 바람직하지 않습니다. 이러한 메서드는 “비공개” 속성으로만 노출하더라도 매력적인 방해 요소가 될 수 있으며(이 PEP의 이전 버전에서 그랬던 것처럼), 결국 그렇게 됩니다. 나중에 누군가 이러한 메서드가 필요하다고 판단하면, 그 시점에 적절한 API(ModuleSpec과는 별개)를 통해 이를 노출할 수 있습니다. 예를 들어 PEP 406(import engine)과 관련된 방식이 될 수 있습니다.

아마도 load() 메서드는 sys.modules 대신 상호작용할 모듈 목록을 선택적으로 받을 수 있을 것입니다. 또한 load()는 다중 버전 임포트를 구현하는 데 활용될 수 있습니다. 둘 다 흥미로운 아이디어이지만, 이 제안의 범위를 확실히 벗어납니다.

제외된 것들:

  • ModuleSpec.submodules(읽기 전용 속성)를 추가합니다 - 스펙에 상대적인 가능한 서브모듈들을 반환합니다.
  • ModuleSpec.loaded(읽기 전용 속성)를 추가합니다 - 있다면 sys.module에 있는 모듈입니다.
  • ModuleSpec.data를 추가합니다 - 스펙의 로더의 데이터 API를 감싸는 디스크립터입니다.
  • [3]도 참조하십시오.

참고 문헌