설명
에서 PEP (328)
상대적 가져 오기는 모듈의 __name__ 속성을 사용하여 패키지 계층에서 해당 모듈의 위치를 결정합니다. 모듈 이름에 패키지 정보가 포함되어 있지 않은 경우 (예 : '__main__'로 설정)
모듈이 실제로 파일 시스템의 위치에 상관없이 모듈이 최상위 모듈 인 것처럼 상대 가져 오기가 해결됩니다 .
어느 시점에서 PEP 338 은 PEP 328 과 충돌했습니다 .
... 상대적 가져 오기는 __name__ 을 사용하여 패키지 계층에서 현재 모듈의 위치를 결정합니다. 기본 모듈에서 __name__ 의 값 은 항상 '__main__'입니다. 이므로 명시 적 상대 가져 오기는 항상 실패합니다 (패키지 내의 모듈에 대해서만 작동하므로)
이 문제를 해결하기 위해 PEP 366 은 최상위 변수를 도입했습니다 __package__
.
새로운 모듈 레벨 속성을 추가하여이 PEP를 사용하면 -m
스위치를 사용하여 모듈을 실행할 경우 상대 가져 오기가 자동으로 작동 합니다. 모듈 자체에 소량의 상용구가 있으면 파일을 이름으로 실행할 때 상대 가져 오기가 작동합니다. [...] [속성]이 있으면 상대 가져 오기는 모듈 __name__ 속성이 아니라이 속성을 기반으로 합니다. [...] 메인 모듈이 파일 이름으로 지정되면 __package__ 속성이 None 으로 설정됩니다 . [...] 가져 오기 시스템에서 __package__가 설정되지 않은 (또는 None으로 설정된) 모듈에서 명시 적 상대 가져 오기가 발생하면 올바른 값을 계산하여 저장합니다 (일반 모듈의 경우 __name __. rpartition ( '.') [0] 및 패키지 초기화 모듈의 경우 __name__ )
(강조 광산)
(가) 경우 __name__
이며 '__main__'
, __name__.rpartition('.')[0]
빈 문자열을 반환합니다. 이것이 오류 설명에 빈 문자열 리터럴이있는 이유입니다.
SystemError: Parent module '' not loaded, cannot perform relative import
CPython PyImport_ImportModuleLevelObject
함수 의 관련 부분 :
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
CPython은 (로 액세스 가능 package
)에서 interp->modules
( 패키지 이름) 을 찾을 수없는 경우이 예외를 발생 sys.modules
시킵니다. 이후 sys.modules
입니다 "이미로드 된 모듈에 모듈 이름을 매핑하는 사전" , 이제 것이 분명 부모 모듈이 명시 적으로 상대 가져 오기를 수행하기 전에-절대 가져와야합니다 .
참고 로부터 패치 문제 18018은 추가 한 다른 if
블록 이 실행되고, 이전 코드 위 :
if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
...
*/
경우 package
(위와 동일)이 빈 문자열, 오류 메시지가 될 것입니다
ImportError: attempted relative import with no known parent package
그러나 Python 3.6 이상에서만 이것을 볼 수 있습니다.
해결 방법 # 1 : -m을 사용하여 스크립트 실행
디렉토리 (Python 패키지 ) 를 고려하십시오 .
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
패키지의 모든 파일 은 동일한 두 줄의 코드로 시작합니다.
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
나는이 두 줄을 포함하고 있습니다 만 분명 작업의 순서를 확인합니다. 실행에 영향을 미치지 않으므로 완전히 무시할 수 있습니다.
__init__.py 및 module.py 에는 두 줄만 포함됩니다 (즉, 실제로 비어 있음).
standalone.py는 추가 로 상대 가져 오기를 통해 module.py 를 가져 오려고 시도합니다 .
from . import module # explicit relative import
우리는 그것이 /path/to/python/interpreter package/standalone.py
실패 할 것이라는 것을 잘 알고 있습니다. 그러나, 우리가 가진 모듈을 실행할 수있는 -m
명령 줄 옵션 것이다 "검색 sys.path
명명 된 모듈과 같은 내용을 실행 __main__
모듈을" :
vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
-m
모든 가져 오기 작업을 수행하고 자동으로 설정합니다. __package__
하지만
해결 방법 # 2 : __package__를 수동으로 설정
실제 솔루션이 아닌 개념 증명으로 취급하십시오. 실제 코드에서 사용하기에 적합하지 않습니다.
PEP 366 은이 문제에 대한 해결 방법이 있지만 설정 __package__
만으로는 충분하지 않으므로 불완전 합니다. 모듈 계층 구조에서 N 개 이상의 선행 패키지 를 가져와야 합니다. 여기서 N 은 가져올 모듈을 검색 할 상위 디렉토리 (스크립트의 디렉토리에 해당)의 수입니다.
그러므로,
현재 모듈 의 N 번째 선행 작업의 상위 디렉토리를 추가하십시오.sys.path
에서 현재 파일의 디렉토리를 제거하십시오 sys.path
정규화 된 이름을 사용하여 현재 모듈의 상위 모듈을 가져옵니다.
2__package__
에서 정규화 된 이름으로 설정
상대적 가져 오기 수행
솔루션 # 1 에서 파일을 빌리고 더 많은 하위 패키지를 추가합니다.
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
이번에는 standalone.py 는 다음과 같은 상대 가져 오기를 사용하여 패키지 패키지 에서 module.py 를 가져옵니다.
from ... import module # N = 3
그 줄 앞에 상용구 코드를 붙여서 작동시켜야합니다.
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[3]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
파일 이름으로 standalone.py 를 실행할 수 있습니다 .
vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
함수에 싸여진보다 일반적인 해결책은 여기 에서 찾을 수 있습니다 . 사용법 예 :
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
해결 방법 # 3 : 절대 가져 오기 및 설정 도구 사용
단계는-
명시 적 상대 수입을 동등한 절대 수입으로 교체
package
가져 오기 가능하도록 설치
예를 들어, 디렉토리 구조는 다음과 같습니다.
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
어디 setup.py가 있다
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
나머지 파일은 솔루션 # 1 에서 빌 렸습니다 .
설치하면 작업 디렉토리에 관계없이 패키지를 가져올 수 있습니다 (이름 지정 문제가 없다고 가정).
이 장점을 사용하기 위해 standalone.py 를 수정할 수 있습니다 (1 단계).
from package import module # absolute import
에 작업 디렉토리로 변경 project
하고 실행 /path/to/python/interpreter setup.py install --user
( --user
에서 패키지를 설치 하여 사이트 패키지 디렉토리 ) (2 단계) :
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
standalone.py 를 스크립트 로 실행할 수 있는지 확인하십시오 :
vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
참고 :이 경로를 사용하기로 결정한 경우 가상 환경 을사용하여패키지를 별도로 설치하는 것이 좋습니다.
해결책 # 4 : 절대 수입품과 일부 상용구 코드 사용
솔직히 설치가 필요하지 않습니다. 스크립트 파일에 상용구 코드를 추가하여 절대적으로 가져올 수 있습니다.
솔루션 # 1 에서 파일을 빌리고 standalone.py를 변경 하겠습니다 .
의 상위 디렉토리 추가 패키지 에 sys.path
전에 에서 아무것도 가져 오려고 패키지 절대 수입을 사용하여 :
import sys
from pathlib import Path # if you haven't already done so
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# Additionally remove the current file's directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
상대 가져 오기를 절대 가져 오기로 바꾸십시오.
from package import module # absolute import
standalone.py 는 문제없이 됩니다.
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
나는 당신에게 경고해야한다고 생각합니다. 특히 프로젝트가 복잡한 구조를 가지고 있다면 .
부수적으로, PEP 8 은 절대 수입의 사용을 권장하지만, 일부 시나리오에서는 명시 적 상대 수입이 허용된다고 명시합니다.
절대적으로 가져 오기는 일반적으로 더 읽기 쉽고 더 나은 동작을하는 경향이 있으므로 (또는 적어도 더 나은 오류 메시지를 표시 함) 권장됩니다. [...] 그러나 명시 적 상대 가져 오기는 절대 가져 오기를 대신 할 수있는 대안입니다. 특히 절대 가져 오기를 사용하는 것이 불필요하게 자세한 복잡한 패키지 레이아웃을 처리 할 때 특히 그렇습니다.