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

Python 개선 제안 한국어 번역

PEP 420 – 암시적 네임스페이스 패키지

Author:
Eric V. Smith <eric at trueblade.com>
Status:
Final
Type:
Standards Track
Created:
19-Apr-2012
Python-Version:
3.3
Post-History:

Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

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

초록

네임스페이스 패키지는 하나의 Python 패키지를 디스크의 여러 디렉터리로 분할하는 메커니즘입니다. 현재 Python 버전에서는 패키지의 __path__를 계산하는 알고리즘을 정식화해야 합니다. 여기서 제안하는 개선 사항을 적용하면 가져오기 메커니즘 자체가 패키지를 구성하는 디렉터리 목록을 생성합니다. 이 PEP는 PEP 382PEP 402에 문서화된 이전 작업을 기반으로 합니다. 이후 이 PEP를 채택하는 대신 해당 PEP들은 거부되었습니다. 이 PEP의 구현은 [1]에 있습니다.

용어

이 PEP에서는 다음과 같이 정의합니다.

  • “패키지”는 Python의 import 문에서 정의하는 Python 패키지를 의미합니다.
  • “배포”는 Python 패키지 색인에 저장되고 distutils 또는 setuptools로 설치할 수 있는, 별도로 설치 가능한 Python 모듈 집합을 의미합니다.
  • “벤더 패키지”는 운영 체제의 패키징 메커니즘으로 설치되는 파일 그룹을 의미합니다(예: Debian 또는 Redhat 패키지는 Linux 시스템에 설치됩니다).
  • “일반 패키지”는 Python 3.2 및 이전 버전에서 구현된 패키지를 의미합니다.
  • “부분”은 네임스페이스 패키지에 기여하는 단일 디렉터리의 파일 집합을 의미합니다(압축 파일에 저장될 수도 있습니다).
  • “레거시 부분”은 네임스페이스 패키지를 구현하기 위해 __path__를 조작하는 부분을 의미합니다.

이 PEP는 새로운 유형의 패키지인 “네임스페이스 패키지”를 정의합니다.

현재의 네임스페이스 패키지

Python은 현재 패키지를 네임스페이스 패키지로 나타내기 위해 pkgutil.extend_path를 제공합니다. 이를 사용하는 권장 방법은 다음과 같습니다.:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

패키지의 __init__.py에 넣습니다. 모든 배포는 __init__.py에 동일한 내용을 제공해야 하며, 이를 통해 패키지의 어느 부분을 먼저 가져오더라도 extend_path가 호출됩니다. 그 결과 패키지의 __init__.py는 패키지 조각이 sys.path에서 배치된 순서에 따라 어느 부분을 먼저 가져올지 결정해야 하므로 실제로 이름을 정의할 수 없습니다. 특별한 기능으로 extend_path<packagename>.pkg라는 이름의 파일을 읽어 추가 부분을 선언할 수 있도록 합니다.

setuptools는 pkg_resources.declare_namespace라는 유사한 함수를 제공하며, 다음 형식으로 사용합니다.:

import pkg_resources
pkg_resources.declare_namespace(__name__)

부분의 __init__.py에서는 __path__에 할당할 필요가 없습니다. declare_namespacesys.modules를 통해 패키지의 __path__를 수정하기 때문입니다. 특별한 기능으로 declare_namespace는 압축 파일도 지원하며, 패키지 이름을 내부적으로 등록하므로 setuptools가 향후 sys.path에 추가하는 항목이 각 패키지에 추가 부분을 올바르게 추가할 수 있습니다.

setuptools는 배포의 setup.py에서 네임스페이스 패키지를 선언할 수 있도록 하므로, 배포 개발자가 직접 __init__.py에 해당 특수 __path__수정 코드를 넣을 필요가 없습니다.

네임스페이스 패키지에 대한 추가적인 동기는 PEP 402“The Problem” 절을 참조하십시오. 참고로 PEP 402는 거부되었지만, 동기를 부여한 사용 사례는 여전히 유효합니다.

근거

현재의 네임스페이스 패키지에 대한 명령형 접근 방식은 네임스페이스 패키지를 제공하기 위한 서로 약간 호환되지 않는 여러 메커니즘을 초래했습니다. 예를 들어, pkgutil은 *.pkg 파일을 지원하지만 setuptools는 지원하지 않습니다. 마찬가지로 setuptools는 zip 파일 검사를 지원하고, _namespace_packages 변수에 부분을 추가하는 것도 지원하지만 pkgutil은 지원하지 않습니다.

네임스페이스 패키지는 여러 디렉터리에 걸쳐 분할되어 존재할 수 있도록 설계되었으며, 따라서 여러 sys.path 항목을 통해 찾을 수 있습니다. 이 구성에서는 각 부분이 네임스페이스 패키지를 올바르게 초기화하기만 한다면 여러 부분이 모두 __init__.py 파일을 제공하더라도 문제가 되지 않습니다. 그러나 Linux 배포판 공급업체는 다른 공급업체와 마찬가지로 분리된 부분을 결합하여 모두 동일한 파일 시스템 디렉터리에 설치하는 방식을 선호합니다. 이로 인해 충돌 가능성이 생깁니다. 이제 각 부분이 대상 시스템에서 동일한 파일을 제공하려고 하기 때문이며, 이는 많은 패키지 관리자에서 허용되지 않습니다. 암시적 네임스페이스 패키지를 허용하면 __init__.py 파일을 제공해야 한다는 요구 사항을 완전히 제거할 수 있으며, 영향을 받는 부분을 공통 디렉터리에 설치하거나 배포판이 적절하다고 판단하는 대로 여러 디렉터리에 분할하여 설치할 수 있습니다.

네임스페이스 패키지는 네임스페이스 패키지 생성 시 부모 경로에서 계산되는 고정된 __path__에 의해 제한되지 않습니다. 표준 라이브러리의 encodings 패키지를 생각해 보십시오.

  1. encodings가 네임스페이스 패키지가 된다고 가정해 보십시오.
  2. 이 패키지는 표준 입출력 스트림을 초기화하기 위해 인터프리터 시작 중에 때때로 가져와집니다.
  3. 애플리케이션이 시작 후 sys.path를 수정하고 새로운 경로 항목의 인코딩을 추가하려고 합니다.
  4. 3단계에서 추가된 경로 항목에서 발견되는 encodings 부분으로부터 인코딩을 가져오려는 시도가 이루어집니다.

가져오기 시스템이 encodings 네임스페이스 패키지가 생성된 시점에 존재했던 sys.path의 값에 따라서만 부분을 찾도록 제한된다면, 3단계에서 추가된 경로는 단계에서 가져오는 추가 부분을 검색하는 데 절대 사용되지 않을 것입니다. 4. 또한 2단계를 어떤 런타임 플래그나 다른 조건으로 인해 때때로 건너뛴다면, 3단계에서 추가된 경로 항목은 실제로 부분을 처음 가져올 때 사용될 것입니다. 따라서 이 PEP에서는 각 부분이 로드될 때 경로 항목 목록을 동적으로 계산하도록 요구합니다. 가져오기 메커니즘은 __path__값을 캐시하고 부모 경로가 변경되었음을 감지할 때만 이를 새로 고침으로써 이 작업을 효율적으로 수행할 것으로 예상됩니다. encodings와 같은 최상위 패키지의 경우 이 부모 경로는 sys.path입니다.

명세

일반 패키지는 계속해서 __init__.py를 포함하며 단일 디렉터리에 위치합니다.

네임스페이스 패키지는 __init__.py를 포함할 수 없습니다. 따라서 네임스페이스 패키지 생성 목적에서 pkgutil.extend_pathpkg_resources.declare_namespace는 더 이상 사용되지 않습니다. 네임스페이스 패키지를 지정하기 위한 마커 파일이나 디렉터리는 존재하지 않습니다.

가져오기 처리 중 가져오기 메커니즘은 Python 3.2에서와 마찬가지로 부모 경로의 각 디렉터리를 계속 순회합니다. 이름이 “foo”인 모듈이나 패키지를 찾을 때 부모 경로의 각 디렉터리에 대해 다음을 수행합니다.

  • <directory>/foo/__init__.py가 발견되면 일반 패키지를 가져와 반환합니다.
  • 그렇지 않지만 <directory>/foo.{py,pyc,so,pyd}가 발견되면 모듈을 가져와 반환합니다. 확장자의 정확한 목록은 플랫폼과 -O 플래그 지정 여부에 따라 달라집니다. 여기의 목록은 대표적인 예입니다.
  • 그렇지 않지만 <directory>/foo가 발견되고 디렉터리이면 이를 기록하고 부모 경로의 다음 디렉터리로 검색을 계속합니다.
  • 그 대신 스캔은 부모 경로의 다음 디렉터리에서 계속됩니다.

스캔이 모듈이나 패키지를 반환하지 않은 채 완료되고 하나 이상의 디렉터리가 기록된 경우 네임스페이스 패키지가 생성됩니다. 새 네임스페이스 패키지는 다음과 같습니다:

  • __path__속성은 스캔 중 발견되어 기록된 경로 문자열의 이터러블로 설정됩니다.
  • __file__속성을 가지지 않습니다.

“import foo”가 실행되고 “foo”가 네임스페이스 패키지로 발견되면(위 규칙을 사용하여), “foo”는 즉시 패키지로 생성됩니다. 네임스페이스 패키지의 생성은 하위 수준 임포트가 발생할 때까지 지연되지 않습니다.

네임스페이스 패키지는 일반 패키지와 근본적으로 다르지 않습니다. 패키지를 생성하는 또 다른 방법일 뿐입니다. 네임스페이스 패키지가 생성되면 해당 패키지와 일반 패키지 사이에는 기능상의 차이가 없습니다.

동적 경로 계산

임포트 시스템은 각 부분을 로드하기 전에 네임스페이스 패키지의 __path__가 다시 계산되는 것처럼 동작합니다.

성능상의 이유로 이는 부모 경로가 변경되었는지 감지하여 구현할 것으로 예상됩니다. 변경이 발생하지 않았다면 __path__를 다시 계산할 필요가 없습니다. 구현에서는 부모 경로의 내용 변경을 감지하고, 부모 경로가 새로운 경로 항목 목록 객체로 교체되는 것도 감지해야 합니다.

임포트 파인더와 로더에 미치는 영향

PEP 302은 경로 요소를 검색하도록 호출되는 “파인더”를 정의합니다. 이러한 파인더의 find_module 메서드는 “로더” 객체 또는 None을 반환합니다.

파인더가 네임스페이스 패키지에 기여하려면 새로운 find_loader(fullname)메서드를 구현해야 합니다. fullnamefind_module의 경우와 같은 의미를 가집니다. find_loader는 항상 (loader, <iterable-of-path-entries>)의 2-튜플을 반환합니다. loaderNone일 수 있으며, 이 경우 <iterable-of-path-entries>(비어 있을 수도 있음)은 기록된 경로 항목 목록에 추가되고 경로 검색이 계속됩니다. loaderNone이 아니면 이를 즉시 사용하여 모듈 또는 일반 패키지를 로드합니다.

loader가 반환되고 None이 아닌 경우에도 <iterable-of-path-entries>에는 패키지의 경로 항목이 여전히 포함되어야 합니다. 이를 통해 pkgutil.extend_path()와 같은 코드가 로드하지 않는 패키지의 경로 항목을 계산할 수 있습니다.

파인더마다 여러 경로 항목을 허용한다는 점에 유의하십시오. 이는 파인더가 지정된 fullname에 대해 여러 네임스페이스 부분을 발견하는 경우를 지원하기 위한 것입니다. 많은 파인더는 find_loader호출마다 하나의 네임스페이스 패키지 부분만 지원하며, 이 경우 이 이터러블에는 문자열 하나만 포함됩니다.

임포트 시스템은 find_loader가 존재하면 이를 호출하고, 그렇지 않으면 find_module로 대체합니다. find_module은 구현하지만 find_loader는 구현하지 않는 레거시 파인더는 네임스페이스 패키지에 부분을 제공할 수 없습니다.

이 명세는 PEP 302 로더에 module_repr()라는 선택적 메서드를 포함하도록 확장하며, 이 메서드가 있으면 모듈 객체의 repr을 생성하는 데 사용됩니다. 자세한 내용은 아래 섹션을 참조하십시오.

네임스페이스 패키지와 일반 패키지의 차이점

네임스페이스 패키지와 일반 패키지는 매우 유사합니다. 차이점은 다음과 같습니다:

  • 네임스페이스 패키지의 부분들은 모두 동일한 디렉터리 구조에서, 또는 동일한 로더에서 올 필요가 없습니다. 일반 패키지는 자체적으로 완결되어 있습니다. 모든 부분이 동일한 디렉터리 계층에 존재합니다.
  • 네임스페이스 패키지에는 __file__ 속성이 없습니다.
  • 네임스페이스 패키지의 __path__ 속성은 문자열로 이루어진 읽기 전용 이터러블이며, 부모 경로가 수정되면 자동으로 업데이트됩니다.
  • 네임스페이스 패키지에는 __init__.py 모듈이 없습니다.
  • 네임스페이스 패키지의 __loader__ 속성에는 다른 유형의 객체가 사용됩니다.

표준 라이브러리의 네임스페이스 패키지

표준 라이브러리의 일부를 네임스페이스 패키지로 구현할 수 있으며, 이 PEP는 이를 명시적으로 허용합니다. 표준 라이브러리 패키지 중 어떤 것이 네임스페이스 패키지가 되는지 여부와 시점은 이 PEP의 범위를 벗어납니다.

레거시 네임스페이스 패키지에서 마이그레이션하기

위에서 설명했듯이, 이 PEP 이전에는 pkgutil.extend_path()은 레거시 부분에서 네임스페이스 패키지를 생성하는 데 사용되었습니다. 기존 네임스페이스 패키지의 모든 부분을 한 번에 이 PEP로 마이그레이션하는 것은 현실적으로 어려울 가능성이 높으므로, extend_path()PEP 420 네임스페이스 패키지도 인식하도록 수정됩니다. 이에 따라 네임스페이스의 일부 부분은 레거시 부분으로 남아 있는 동안 다른 부분은 PEP 420으로 마이그레이션할 수 있습니다. 이러한 하이브리드 네임스페이스 패키지에는 일반 네임스페이스 패키지가 제공하는 동적 경로 계산 기능이 없습니다. 과거에 extend_path()는 이 기능을 제공한 적이 없기 때문입니다.

패키징 관련 영향

네임스페이스 패키지의 여러 부분을 동일한 디렉터리 또는 서로 다른 디렉터리에 설치할 수 있습니다. 이 절에서는 “foo.bar”와 “foo.baz”를 정의하는 두 부분이 있다고 가정합니다. “foo” 자체는 네임스페이스 패키지입니다.

이들을 동일한 위치에 설치하면, sys.path에 있는 디렉터리 안에 “foo”라는 단일 디렉터리가 존재하게 됩니다. “foo” 안에는 “bar”와 “baz”라는 두 디렉터리가 존재하게 됩니다. “foo.bar”가 제거되는 경우(운영 체제 패키지 관리자에 의해 제거될 수도 있습니다), “foo/baz” 디렉터리나 “foo” 디렉터리를 제거하지 않도록 주의해야 합니다. 이 경우 모든 부분이 동일한 디렉터리에 있더라도 “foo”는 네임스페이스 패키지가 된다는 점에 유의하십시오. __init__.py가 없기 때문입니다.

“foo.bar”와 “foo.baz”는 서로 공통된 파일이 없으므로 동일한 “foo” 디렉터리에 설치할 수 있다는 점에 유의하십시오.

부분들을 서로 다른 위치에 설치하면, sys.path에 있는 디렉터리 안에 서로 다른 “foo” 디렉터리 두 개가 존재하게 됩니다. “foo/bar”는 이러한 sys.path 항목 중 하나에 존재하고, “foo/baz”는 다른 항목에 존재하게 됩니다. “foo.bar”를 제거하면 “foo/bar” 디렉터리와 이에 대응하는 “foo” 디렉터리를 완전히 제거할 수 있습니다. 그러나 “foo/baz”와 이에 대응하는 “foo” 디렉터리는 제거할 수 없습니다.

“foo.bar” 부분을 sys.path에 있는 디렉터리에 설치하고, “foo.baz” 부분을 역시 sys.path에 있는 zip 파일에서 제공할 수도 있습니다.

예제

중첩된 네임스페이스 패키지

이 예제에서는 다음 디렉터리 구조를 사용합니다.:

Lib/test/namespace_pkgs
    project1
        parent
            child
                one.py
    project2
        parent
            child
                two.py

여기서는 부모와 자식이 모두 네임스페이스 패키지입니다. 이들의 부분은 서로 다른 디렉터리에 존재하며, __init__.py파일이 없습니다.

여기서는 부모 디렉터리들을 sys.path에 추가하고, 부분들이 올바르게 검색됨을 보여 줍니다.:

>>> import sys
>>> sys.path += ['Lib/test/namespace_pkgs/project1', 'Lib/test/namespace_pkgs/project2']
>>> import parent.child.one
>>> parent.__path__
_NamespacePath(['Lib/test/namespace_pkgs/project1/parent', 'Lib/test/namespace_pkgs/project2/parent'])
>>> parent.child.__path__
_NamespacePath(['Lib/test/namespace_pkgs/project1/parent/child', 'Lib/test/namespace_pkgs/project2/parent/child'])
>>> import parent.child.two
>>>

동적 경로 계산

이 예제는 유사한 디렉터리 구조를 사용하지만, 세 번째 부분을 추가합니다.:

Lib/test/namespace_pkgs
    project1
        parent
            child
                one.py
    project2
        parent
            child
                two.py
    project3
        parent
            child
                three.py

project1project2sys.path에 추가한 다음, parent.child.oneparent.child.two를 임포트합니다. 그런 다음 project3sys.path에 추가하면, parent.child.three를 임포트할 때 project3/parent가 자동으로 parent.__path__에 추가됩니다.:

# add the first two parent paths to sys.path
>>> import sys
>>> sys.path += ['Lib/test/namespace_pkgs/project1', 'Lib/test/namespace_pkgs/project2']

# parent.child.one can be imported, because project1 was added to sys.path:
>>> import parent.child.one
>>> parent.__path__
_NamespacePath(['Lib/test/namespace_pkgs/project1/parent', 'Lib/test/namespace_pkgs/project2/parent'])

# parent.child.__path__ contains project1/parent/child and project2/parent/child, but not project3/parent/child:
>>> parent.child.__path__
_NamespacePath(['Lib/test/namespace_pkgs/project1/parent/child', 'Lib/test/namespace_pkgs/project2/parent/child'])

# parent.child.two can be imported, because project2 was added to sys.path:
>>> import parent.child.two

# we cannot import parent.child.three, because project3 is not in the path:
>>> import parent.child.three
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 1286, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1250, in _find_and_load_unlocked
ImportError: No module named 'parent.child.three'

# now add project3 to sys.path:
>>> sys.path.append('Lib/test/namespace_pkgs/project3')

# and now parent.child.three can be imported:
>>> import parent.child.three

# project3/parent has been added to parent.__path__:
>>> parent.__path__
_NamespacePath(['Lib/test/namespace_pkgs/project1/parent', 'Lib/test/namespace_pkgs/project2/parent', 'Lib/test/namespace_pkgs/project3/parent'])

# and project3/parent/child has been added to parent.child.__path__
>>> parent.child.__path__
_NamespacePath(['Lib/test/namespace_pkgs/project1/parent/child', 'Lib/test/namespace_pkgs/project2/parent/child', 'Lib/test/namespace_pkgs/project3/parent/child'])
>>>

논의

PyCon 2012에서 네임스페이스 패키지에 관해 논의했으며, 그 논의에서 PEP 382PEP 402는 거부되고 이 PEP [3]로 대체되었습니다.

일반 패키지에 대한 지원을 제거할 의도는 없습니다. 개발자가 자신의 패키지가 네임스페이스 패키지의 부분이 되지 않을 것임을 알고 있다면, 해당 패키지를 일반 패키지(__init__.py를 포함하는 패키지)로 만드는 편이 성능상 유리합니다. 일반 패키지는 경로에서 발견되는 즉시 생성하고 로드할 수 있습니다. 네임스페이스 패키지의 경우 패키지를 생성하기 전에 경로의 모든 항목을 검색해야 합니다.

__init__.py 파일이 없는 디렉터리에 대해서는 더 이상 ImportWarning이 발생하지 않는다는 점에 유의하십시오. 이제 이러한 디렉터리는 네임스페이스 패키지로 임포트되지만, 이전 Python 버전에서는 ImportWarning이 발생했습니다.

Alyssa (Nick) Coghlan은 이 제안에 대한 자신의 이의 목록 [4]을 제시했습니다. 그 내용은 다음과 같습니다.

  1. 암시적 패키지 디렉터리는 Python의 선(禪)에 어긋납니다.
  2. 암시적 패키지 디렉터리는 난처한 하위 호환성 문제를 일으킵니다.
  3. 암시적 패키지 디렉터리는 파일 시스템 레이아웃에 모호성을 초래합니다.
  4. 암시적 패키지 디렉터리는 __main__에서 현재의 초보자에게 적대적인 동작을 영구적으로 고착시킵니다.

Alyssa는 나중에 자신의 이의에 대해 상세한 답변 [5]을 제시했으며, 그 내용은 다음과 같이 요약됩니다.

  1. 이 PEP의 실용성이 다른 제안과 현재 상태를 능가합니다.
  2. 사소한 하위 호환성 문제는 제대로 문서화하기만 한다면 괜찮습니다.
  3. 이는 PEP 395에서 다루게 됩니다.
  4. 이 역시 PEP 395에서 다루게 됩니다.

표준 라이브러리에 네임스페이스 패키지를 포함하게 된 계기는 Martin v. Löwis가 encodings패키지를 네임스페이스 패키지로 만들기를 원했기 때문입니다 [6]. 이 PEP는 표준 라이브러리 패키지가 네임스페이스가 되는 것을 허용하지만, encodings에 대한 결정은 유보합니다.

find_modulefind_loader

이 PEP의 초기 초안에서는 네임스페이스 패키지를 지원하기 위해 find_module 메서드를 변경하도록 명시했습니다. 네임스페이스 패키지의 부분이 발견된 경우 문자열을 반환하도록 수정될 예정이었습니다.

그러나 이는 표준 라이브러리 외부에서 find_module을 호출하는 기존 코드에 문제를 일으켰습니다. 이 코드는 이 PEP에서 요구하는 변경 사항과 함께 업그레이드되지 않으므로, find_module에서 예상하지 못한 반환 값을 받으면 실패하게 됩니다. 이러한 비호환성 때문에 이 PEP는 네임스페이스 부분을 제공하려는 파인더가 위에서 설명한 find_loader 메서드를 구현해야 한다고 이제 명시합니다.

find_loader 호출당 여러 부분을 지원하는 사용 사례는 [7]에 제시되어 있습니다.

동적 경로 계산

Guido는 자동 동적 경로 계산이 불필요한 기능이라는 우려를 제기했습니다 [8]. 이후 해당 스레드에서 PJ Eby와 Alyssa Coghlan은 동적 계산이 Python 사용자들의 예기치 않은 동작을 최소화하는 이유에 대한 근거를 제시했습니다. 해당 논의의 결론은 이 PEP의 근거 섹션에 포함되었습니다.

이 PEP의 이전 버전에서는 부모 경로 객체가 제자리에서 수정되는 경우에만 동적 경로 계산이 적용될 수 있도록 요구했습니다. 즉, 다음은 작동합니다.:

sys.path.append('new-dir')

하지만 다음은 작동하지 않습니다.:

sys.path = sys.path + ['new-dir']

같은 스레드 [8]에서 이 제한은 필요하지 않다는 점이 지적되었습니다. 부모 경로를 참조를 보유하는 대신 이름으로 조회하면 부모 경로가 수정되거나 대체되는 방식에 제한이 없습니다. 최상위 네임스페이스 패키지의 경우 조회 대상은 "sys"라는 이름의 모듈과 그 모듈의 "path" 속성입니다. 패키지 foo 안에 중첩된 네임스페이스 패키지의 경우 조회 대상은 "foo"라는 이름의 모듈과 그 모듈의 "__path__" 속성입니다.

모듈 repr

이전에는 모듈의 repr을 모듈의 __file__속성에 대한 가정을 바탕으로 하드 코딩했습니다. 이 속성이 존재하고 문자열이면 파일 시스템 경로로 간주했으며, 모듈 객체의 repr에는 그 값이 포함되었습니다. 유일한 예외는 PEP 302에서 __file__속성이 없는 것을 내장 모듈에 예약했다는 점이며, CPython에서는 이 가정이 모듈 객체의 구현에 내장되어 있었습니다. 이 제한 때문에 일부 모듈에는 파일 시스템 경로를 반영하지 않는 인위적인 __file__값이 포함되었으며, 이로 인해 나중에 예기치 않은 문제가 발생할 수 있었습니다(예: 경로가 아닌 __file__os.path.join()을 적용하면 의미 없는 값이 반환됩니다).

이 PEP에서는 이 제약을 완화하고 __file__설정을 모듈을 생성하는 로더의 소관으로 둡니다. 적절한 파일 시스템 경로가 없는 경우 로더는 __file__을 설정하지 않을 수 있습니다. 로더는 유용하다면 모듈에 추가 예약 속성을 설정할 수도 있습니다. 이는 모듈의 출처를 확인하는 확실한 방법이 해당 모듈의 __loader__속성을 확인하는 것임을 의미합니다.

예를 들어, 이 PEP에서 설명하는 네임스페이스 패키지에는 해당하는 파일이 존재하지 않으므로 __file__속성이 없습니다. 이러한 모듈의 repr에 유연성과 설명력을 제공하기 위해 PEP 302 로더에 새로운 선택적 프로토콜이 추가됩니다. 로더는 모듈 객체를 단일 인자로 받는 module_repr()메서드를 구현할 수 있습니다. 이 메서드는 모듈의 repr로 그대로 사용할 문자열을 반환해야 합니다. 이제 모듈 repr을 생성하는 규칙은 다음과 같이 표준화됩니다.

  • 모듈에 __loader__가 있고 해당 로더에 module_repr()메서드가 있으면 모듈 객체를 단일 인자로 하여 호출합니다. 반환된 값이 모듈의 repr로 사용됩니다.
  • module_repr()에서 예외가 발생하면 해당 예외를 포착하여 무시하고, module_repr()이 존재하지 않았던 것처럼 모듈 repr 계산을 계속합니다.
  • 모듈에 __file__속성이 있으면 이 속성이 모듈 repr의 일부로 사용됩니다.
  • 모듈에 __file__이 없지만 __loader__가 있으면 로더의 repr이 모듈 repr의 일부로 사용됩니다.
  • 그렇지 않으면 repr에서 모듈의 __name__만 사용합니다.

다음은 로더에서 네임스페이스 모듈 repr이 계산되는 방식을 보여 주는 코드 조각입니다.:

class NamespaceLoader:
    @classmethod
    def module_repr(cls, module):
        return "<module '{}' (namespace)>".format(module.__name__)

내장 모듈의 repr도 더 이상 하드코딩할 필요가 없으며, 대신 로더로부터 가져오게 됩니다:

class BuiltinImporter:
    @classmethod
    def module_repr(cls, module):
        return "<module '{}' (built-in)>".format(module.__name__)

관련 속성들의 서로 다른 집합을 가진 다양한 유형의 모듈에 대한 repr 예시들은 다음과 같습니다:

>>> import email
>>> email
<module 'email' from '/home/barry/projects/python/pep-420/Lib/email/__init__.py'>
>>> m = type(email)('foo')
>>> m
<module 'foo'>
>>> m.__file__ = 'zippy:/de/do/dah'
>>> m
<module 'foo' from 'zippy:/de/do/dah'>
>>> class Loader: pass
...
>>> m.__loader__ = Loader
>>> del m.__file__
>>> m
<module 'foo' (<class '__main__.Loader'>)>
>>> class NewLoader:
...   @classmethod
...   def module_repr(cls, module):
...      return '<mystery module!>'
...
>>> m.__loader__ = NewLoader
>>> m
<mystery module!>
>>>

참고 자료