Python 3의 상대적 가져 오기
동일한 디렉토리의 다른 파일에서 함수를 가져오고 싶습니다.
때로는 나를 위해 작동 from .mymodule import myfunction
하지만 때로는 다음을 얻습니다.
SystemError: Parent module '' not loaded, cannot perform relative import
때로는에서 작동 from mymodule import myfunction
하지만 때로는 다음을 얻습니다.
SystemError: Parent module '' not loaded, cannot perform relative import
나는 여기서 논리를 이해하지 못하고 설명을 찾을 수 없습니다. 이것은 완전히 무작위로 보입니다.
누군가이 모든 논리가 무엇인지 설명해 주시겠습니까?
안타깝게도이 모듈은 패키지 내부에 있어야하며 때로는 스크립트로 실행 가능해야합니다. 내가 어떻게 이룰 수 있는지 아십니까?
다음과 같은 레이아웃을 갖는 것은 매우 일반적입니다.
main.py
mypackage/
__init__.py
mymodule.py
myothermodule.py
... mymodule.py
이렇게 ...
#!/usr/bin/env python3
# Exported function
def as_int(a):
return int(a)
# Test function for module
def _test():
assert as_int('1') == 1
if __name__ == '__main__':
_test()
... myothermodule.py
이렇게 ...
#!/usr/bin/env python3
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __name__ == '__main__':
_test()
... 그리고 main.py
이와 비슷한 ...
#!/usr/bin/env python3
from mypackage.myothermodule import add
def main():
print(add('1', '1'))
if __name__ == '__main__':
main()
... main.py
또는 실행하면 잘 작동 mypackage/mymodule.py
하지만 mypackage/myothermodule.py
상대 가져 오기로 인해 실패합니다 ...
from .mymodule import as_int
실행해야하는 방식은 ...
python3 -m mypackage.myothermodule
...하지만 다소 장황하고 #!/usr/bin/env python3
. 와 같은 shebang 라인과 잘 섞이지 않습니다 .
이 경우에 대한 가장 간단한 수정은 이름 mymodule
이 전역 적으로 고유 하다고 가정하고 상대 가져 오기를 사용하지 않고 다음을 사용하는 것입니다.
from mymodule import as_int
... 독특하지 않거나 패키지 구조가 더 복잡한 경우 패키지 디렉토리가 포함 된 디렉토리를에 포함 PYTHONPATH
하고 다음과 같이 수행해야합니다.
from mypackage.mymodule import as_int
... 또는 "즉시"작동하도록하려면 먼저이 PYTHONPATH
코드를 사용하여 코드를 만들 수 있습니다 .
import sys
import os
PACKAGE_PARENT = '..'
SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))
from mypackage.mymodule import as_int
일종의 고통이지만 특정 Guido van Rossum이 작성한 이메일 에 이유에 대한 단서가 있습니다 .
나는 이것과
__main__
기계 의 다른 제안 된 트위들 링에서 -1입니다 . 유일한 유스 케이스는 모듈의 디렉토리 안에있는 스크립트를 실행하는 것 같습니다. 저는 항상 안티 패턴으로 보았습니다. 제 마음을 바꾸려면 그렇지 않다는 것을 저를 설득해야합니다.
내가 표시 할 소스 파일 중 어떤 스크립트를 실행할 수 있도록 패키지 내부에 스크립트를 실행 여부, 안티 패턴은인지 주관적이지만, 개인적으로는 일부 사용자 지정 wxPython에 위젯을 포함 내가 가진 패키지에서 정말 유용 wx.Frame
만 포함을 테스트 목적으로 해당 위젯.
설명
에서 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 : 절대 가져 오기 및 일부 상용구 코드 사용
Frankly, the installation is not necessary - you could add some boilerplate code to your script to make absolute imports work.
I'm going to borrow files from Solution #1 and change standalone.py:
Add the parent directory of package to
sys.path
before attempting to import anything from package using absolute imports: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
Replace the relative import by the absolute import:
from package import module # absolute import
standalone.py runs without problems:
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'>
I feel that I should warn you: try not to do this, especially if your project has a complex structure.
As a side note, PEP 8 recommends the use of absolute imports, but states that in some scenarios explicit relative imports are acceptable:
Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages). [...] However, explicit relative imports are an acceptable alternative to absolute imports, especially when dealing with complex package layouts where using absolute imports would be unnecessarily verbose.
Put this inside your package's __init__.py file:
# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
Assuming your package is like this:
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module1.py
│ │ └── module2.py
│ └── setup.py
Now use regular imports in you package, like:
# in module2.py
from module1 import class1
This works in both python 2 and 3.
I ran into this issue. A hack workaround is importing via an if/else block like follows:
#!/usr/bin/env python3
#myothermodule
if __name__ == '__main__':
from mymodule import as_int
else:
from .mymodule import as_int
# Exported function
def add(a, b):
return as_int(a) + as_int(b)
# Test function for module
def _test():
assert add('1', '1') == 2
if __name__ == '__main__':
_test()
Hopefully, this will be of value to someone out there - I went through half a dozen stackoverflow posts trying to figure out relative imports similar to whats posted above here. I set up everything as suggested but I was still hitting ModuleNotFoundError: No module named 'my_module_name'
Since I was just developing locally and playing around, I hadn't created/run a setup.py
file. I also hadn't apparently set my PYTHONPATH
.
I realized that when I ran my code as I had been when the tests were in the same directory as the module, I couldn't find my module:
$ python3 test/my_module/module_test.py 2.4.0
Traceback (most recent call last):
File "test/my_module/module_test.py", line 6, in <module>
from my_module.module import *
ModuleNotFoundError: No module named 'my_module'
However, when I explicitly specified the path things started to work:
$ PYTHONPATH=. python3 test/my_module/module_test.py 2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s
OK
So, in the event that anyone has tried a few suggestions, believes their code is structured correctly and still finds themselves in a similar situation as myself try either of the following if you don't export the current directory to your PYTHONPATH:
- Run your code and explicitly include the path like so:
$ PYTHONPATH=. python3 test/my_module/module_test.py
- To avoid calling
PYTHONPATH=.
, create asetup.py
file with contents like the following and runpython setup.py development
to add packages to the path:
# setup.py from setuptools import setup, find_packages setup( name='sample', packages=find_packages() )
To obviate this problem, I devised a solution with the repackage package, which has worked for me for some time. It adds the upper directory to the lib path:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Repackage can make relative imports that work in a wide range of cases, using an intelligent strategy (inspecting the call stack).
I needed to run python3 from the main project directory to make it work.
For example, if the project has the following structure:
project_demo/
├── main.py
├── some_package/
│ ├── __init__.py
│ └── project_configs.py
└── test/
└── test_project_configs.py
Solution
I would run python3 inside folder project_demo/ and then perform a
from some_package import project_configs
if both packages are in your import path (sys.path), and the module/class you want is in example/example.py, then to access the class without relative import try:
from example.example import fkt
참고URL : https://stackoverflow.com/questions/16981921/relative-imports-in-python-3
'Development Tip' 카테고리의 다른 글
Node.js로 명령 줄 바이너리 실행 (0) | 2020.10.03 |
---|---|
배열에 JavaScript / jQuery의 특정 문자열이 포함되어 있는지 확인하는 방법은 무엇입니까? (0) | 2020.10.03 |
Math.round (0.49999999999999994)가 1을 반환하는 이유는 무엇입니까? (0) | 2020.10.03 |
Visual Studio의 출력 창에 쓰기 (0) | 2020.10.03 |
C # 목록 (0) | 2020.10.03 |