[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.pymypackage/mymodule.py,하지만 실패 mypackage/myothermodule.py로 인해 상대적 가져 오기에 …

from .mymodule import as_int

당신이 그것을 실행하는 방식은 …

python3 -m mypackage.myothermodule

… 그러나 다소 장황하고 같은 shebang 라인과 잘 섞이지 않습니다 #!/usr/bin/env python3.

이름 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 338PEP 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__.pymodule.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 은 가져올 모듈을 검색 할 상위 디렉토리 (스크립트의 디렉토리에 해당)의 수입니다.

그러므로,

  1. 현재 모듈 의 N 번째 선행 작업의 상위 디렉토리를 추가하십시오.sys.path

  2. 에서 현재 파일의 디렉토리를 제거하십시오 sys.path

  3. 정규화 된 이름을 사용하여 현재 모듈의 상위 모듈을 가져옵니다.

  4. 2__package__ 에서 정규화 된 이름으로 설정

  5. 상대적 가져 오기 수행

솔루션 # 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 : 절대 가져 오기 및 설정 도구 사용

단계는-

  1. 명시 적 상대 수입을 동등한 절대 수입으로 교체

  2. 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를 변경 하겠습니다 .

  1. 의 상위 디렉토리 추가 패키지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
  2. 상대 가져 오기를 절대 가져 오기로 바꾸십시오.

    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 은 절대 수입의 사용을 권장하지만, 일부 시나리오에서는 명시 적 상대 수입이 허용된다고 명시합니다.

절대적으로 가져 오기는 일반적으로 더 읽기 쉽고 더 나은 동작을하는 경향이 있으므로 (또는 적어도 더 나은 오류 메시지를 표시 함) 권장됩니다. […] 그러나 명시 적 상대 가져 오기는 절대 가져 오기를 대신 할 수있는 대안입니다. 특히 절대 가져 오기를 사용하는 것이 불필요하게 자세한 복잡한 패키지 레이아웃을 처리 할 때 특히 그렇습니다.


답변

이것을 패키지의 __init__.py 파일 안에 넣으십시오 .

# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

패키지가 다음과 같다고 가정합니다.

├── project
   ├── package
      ├── __init__.py
      ├── module1.py
      └── module2.py
   └── setup.py

이제 다음과 같이 패키지에서 정기적으로 가져 오기를 사용하십시오.

# in module2.py
from module1 import class1

이것은 파이썬 2와 3 모두에서 작동합니다.


답변

나는이 문제에 부딪쳤다. 해킹 해결 방법은 다음과 같은 if / else 블록을 통해 가져옵니다.

#!/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()


답변

바라건대, 이것은 누군가에게 가치가있을 것입니다-나는 위에 게시 된 것과 비슷한 상대적 수입을 알아 내려고하는 6 개의 스택 오버 플로우 게시물을 겪었습니다. 나는 제안대로 모든 것을 설정했지만 여전히 타격을 받고 있었다ModuleNotFoundError: No module named 'my_module_name'

로컬에서 개발하고 놀았 기 때문에 setup.py파일을 만들거나 실행하지 않았습니다 . 또한 분명히 내 설정하지 않았습니다 PYTHONPATH.

테스트가 모듈과 동일한 디렉토리에있을 때와 같이 코드를 실행했을 때 모듈을 찾을 수 없다는 것을 깨달았습니다.

$ 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'

그러나 경로를 명시 적으로 지정하면 작업이 시작되었습니다.

$ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s

OK

따라서 누군가가 몇 가지 제안을 시도한 경우 코드가 올바르게 구성되어 있다고 생각하고 현재 디렉토리를 PYTHONPATH로 내 보내지 않으면 다음 중 하나를 시도해보십시오.

  1. 코드를 실행하고 다음과 같이 경로를 명시 적으로 포함하십시오.
    $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. 을 호출하지 않으 PYTHONPATH=.려면 setup.py다음과 같은 내용 으로 파일을 작성하고 python setup.py development패키지를 경로에 추가하십시오.
# setup.py
from setuptools import setup, find_packages

setup(
    name='sample',
    packages=find_packages()
)

답변

메인 프로젝트 디렉토리에서 python3을 실행해야 작동했습니다.

예를 들어 프로젝트의 구조가 다음과 같은 경우

project_demo/
├── main.py
├── some_package/
   ├── __init__.py
   └── project_configs.py
└── test/
    └── test_project_configs.py

해결책

project_demo / 폴더 내에서 python3을 실행 한 다음

from some_package import project_configs


답변

이 문제를 피하기 위해 패키지 패키지 로 솔루션을 고안 했습니다. lib 경로에 상위 디렉토리를 추가합니다.

import repackage
repackage.up()
from mypackage.mymodule import myfunction

리 패키지는 지능형 전략 (콜 스택 검사)을 사용하여 광범위한 사례에서 작동하는 상대적 가져 오기를 수행 할 수 있습니다.