Development Tip

Python 3의 상대적 가져 오기

yourdevel 2020. 10. 3. 12:04
반응형

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 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 : 절대 가져 오기 및 일부 상용구 코드 사용

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:

  1. 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
    
  2. 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:

  1. Run your code and explicitly include the path like so: $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. To avoid calling PYTHONPATH=., create a setup.py file with contents like the following and run python 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

반응형