[python] 10 억 시간 동안의 상대적 수입

나는 여기에 있었다 :

내가 신속하게 솔루션을 가지고 있다고 생각했을 때 복사하지 않은 많은 URL, 일부는 SO, 일부는 다른 사이트에 있습니다.

영원히 반복되는 질문은 다음과 같습니다. Windows 7, 32 비트 Python 2.7.3에서 “비 패키지에서 상대적인 가져 오기 시도”메시지를 어떻게 해결합니까? pep-0328에서 패키지의 정확한 복제본을 작성했습니다.

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

콘솔에서 가져 오기가 수행되었습니다.

적절한 모듈에서 스팸 및 계란이라는 기능을 만들었습니다. 당연히 작동하지 않았습니다. 대답은 분명히 내가 나열된 네 번째 URL에 있지만 모두 나에게 동문입니다. 내가 방문한 URL 중 하나에이 응답이있었습니다.

상대적 가져 오기는 모듈의 이름 속성을 사용하여 패키지 계층에서 해당 모듈의 위치를 ​​결정합니다. 모듈 이름에 패키지 정보가 포함되어 있지 않은 경우 (예 : ‘main’으로 설정) 모듈이 실제로 파일 시스템의 위치에 상관없이 모듈이 최상위 모듈 인 것처럼 상대적 가져 오기가 해결됩니다.

위의 답변은 유망 해 보이지만 모든 상형 문자입니다. 그래서 제 질문은 어떻게 파이썬이 “비 패키지의 상대적인 가져 오기 시도”로 돌아 가지 않게합니까? 아마도 -m과 관련된 대답이 있습니다.

파이썬은 당신이 ‘패키지’를 정의합니까 왜, 어떻게 “비 패키지”, 그리고 무엇을 의미하는지 그 오류 메시지가 제공 왜 누군가가 말해 주시겠습니까 쉽게 충분히 원아가 이해하는 측면에서 정확한 답 넣어 .



답변

스크립트 대 모듈

여기에 설명이 있습니다. 짧은 버전은 Python 파일을 직접 실행하는 것과 다른 곳에서 해당 파일을 가져 오는 것 사이에 큰 차이가 있다는 것입니다. 파일이 어떤 디렉토리에 있는지 아는 것만으로도 파이썬이 어떤 패키지에 있다고 생각하는지 결정할 수 없습니다. 또한 파일을 파이썬에로드하는 방법 (실행 또는 가져 오기)에 따라 다릅니다.

Python 파일을로드하는 방법에는 최상위 스크립트 또는 모듈의 두 가지 방법이 있습니다. python myfile.py명령 줄 에 입력하여 파일 을 직접 실행하면 파일이 최상위 스크립트로로드됩니다 . 할 python -m myfile경우 또는 import다른 파일 에서 명령문이 발견 될 때로드되는 경우 모듈 로로드됩니다 . 한 번에 하나의 최상위 스크립트 만있을 수 있습니다. 최상위 스크립트는 작업을 시작하기 위해 실행 한 Python 파일입니다.

명명

파일이로드되면 이름이 부여됩니다 ( __name__속성에 저장 됨 ). 최상위 스크립트로로드 된 경우 이름은 __main__입니다. 모듈로로드 된 경우 해당 이름은 파일 이름이며 그 앞에 부분으로 포함 된 패키지 / 하위 패키지의 이름이 점으로 구분됩니다.

예를 들어 다음과 같습니다.

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

가져온 경우 moduleX(참고 : 직접 실행하지 않고 가져온 ) 이름은입니다 package.subpackage1.moduleX. 가져온 경우 moduleA이름은입니다 package.moduleA. 그러나 명령 행에서 직접 실행 하면 moduleX이름이 대신 __main__이고 moduleA명령 행에서 직접 실행하면 이름은입니다 __main__. 모듈을 최상위 스크립트로 실행하면 일반 이름이 손실되고 대신 이름이 변경됩니다 __main__.

포함 된 패키지를 통하지 않고 모듈에 액세스

추가 주름이 있습니다. 모듈 이름은 모듈이있는 디렉토리에서 “직접”가져 왔는지 또는 패키지를 통해 가져 왔는지에 따라 다릅니다. 디렉토리에서 Python을 실행하고 동일한 디렉토리 (또는 그 서브 디렉토리)에 파일을 가져 오려는 경우에만 차이가 있습니다. 예를 들어, 당신은 디렉토리에 파이썬 인터프리터를 시작하면 package/subpackage1다음 수행 import moduleX의 이름은 moduleX단지 것 moduleX, 그리고 package.subpackage1.moduleX. 파이썬은 시작할 때 현재 디렉토리를 검색 경로에 추가하기 때문입니다. 현재 디렉토리에서 가져올 모듈을 찾으면 해당 디렉토리가 패키지의 일부임을 알 수 없으며 패키지 정보는 모듈 이름의 일부가되지 않습니다.

인터프리터를 대화식으로 실행하는 경우 (예를 들어, python파이썬 코드를 즉시 입력하고 시작하기 만하면) 특별한 경우가 있습니다 . 이 경우 해당 대화식 세션의 이름은입니다 __main__.

이제 오류 메시지의 중요한 사항은 다음과 같습니다 . 모듈 이름에 점이 없으면 패키지의 일부로 간주되지 않습니다 . 파일이 실제로 디스크에있는 위치는 중요하지 않습니다. 중요한 것은 이름이 무엇이며 이름은로드 한 방법에 따라 다릅니다.

이제 질문에 포함 된 인용문을보십시오.

상대적 가져 오기는 모듈의 이름 속성을 사용하여 패키지 계층에서 해당 모듈의 위치를 ​​결정합니다. 모듈 이름에 패키지 정보가 포함되어 있지 않은 경우 (예 : ‘main’으로 설정) 모듈이 실제로 파일 시스템의 위치에 상관없이 모듈이 최상위 모듈 인 것처럼 상대적 가져 오기가 해결됩니다.

상대 수입품 …

상대적 가져 오기는 모듈 이름 을 사용하여 패키지 내 위치를 결정합니다. 와 같은 상대 가져 오기를 사용하는 from .. import foo경우 점은 패키지 계층에서 몇 가지 레벨을 단계적으로 표시합니다. 예를 들어, 현재 모듈의 이름이 package.subpackage1.moduleX인 경우 ..moduleA평균 package.moduleA입니다. A가 들어 from .. import일하기에 있기 때문에, 모듈의 이름은 많은 점으로 최소한 있어야합니다 import문.

… 패키지에서만 상대적

그러나 모듈 이름이 __main__인 경우 패키지에있는 것으로 간주되지 않습니다. 이름에는 점이 없으므로 그 from .. import안에 문을 사용할 수 없습니다 . 이렇게하면 “비 패키지의 상대 가져 오기”오류가 발생합니다.

스크립트는 상대를 가져올 수 없습니다

아마도 당신이 한 일은 moduleX명령 행에서 실행 하는 것입니다. 이 작업을 수행 할 때 이름이로 설정되었습니다 __main__. 즉, 이름이 패키지에 있음을 나타내지 않기 때문에 그 안에있는 상대적 가져 오기에 실패합니다. 모듈이있는 동일한 디렉토리에서 Python을 실행 한 다음 해당 모듈을 가져 오려고 시도하는 경우에도 마찬가지입니다. 위에서 설명한 것처럼 Python은 현재 디렉토리에서 모듈을 인식하지 않고 “너무 일찍”찾을 수 있기 때문입니다. 패키지의 일부.

또한 대화식 인터프리터를 실행할 때 해당 대화식 세션의 “이름”은 항상 __main__입니다. 따라서 대화식 세션에서 직접 상대 가져 오기를 수행 할 수 없습니다 . 상대적 가져 오기는 모듈 파일 내에서만 사용됩니다.

두 가지 솔루션 :

  1. 실제로 moduleX직접 실행 하고 싶지만 여전히 패키지의 일부로 간주하려면을 수행 할 수 있습니다 python -m package.subpackage1.moduleX. 은 -m하지 최상위 스크립트로, 모듈로로드 파이썬을 알려줍니다.

  2. 또는 실제로 실행 하고 싶지 않고 내부 함수 를 사용 하는 moduleX다른 스크립트를 실행하고 싶을 수도 있습니다 . 그런 경우, 넣어 다른 곳하지 내부 디렉토리 -하고 실행합니다. 내부에서 와 같은 작업을 수행하면 제대로 작동합니다.myfile.pymoduleXmyfile.py packagemyfile.pyfrom package.moduleA import spam

노트

  • 이러한 솔루션 중 하나에 대해 패키지 디렉토리 ( package예 :)는 Python 모듈 검색 경로 ( sys.path) 에서 액세스 할 수 있어야합니다 . 그렇지 않으면 패키지의 어떤 것도 안정적으로 사용할 수 없습니다.

  • 파이썬 2.6부터, 패키지 해결을위한 모듈의 “이름”은 __name__속성뿐만 아니라 속성에 의해서도 결정 됩니다 __package__. 그렇기 때문에 __name__모듈의 “이름”을 나타 내기 위해 명시 적 기호 를 사용하지 않는 이유 입니다. 파이썬 2.6 모듈의 “이름”효율적이기 때문에 __package__ + '.' + __name__, 아니면 그냥 __name__경우 __package__입니다 None.)


답변

이것은 실제로 파이썬 내에서 문제입니다. 혼란의 기원은 사람들이 실수를 상대 경로를 경로 상대 경로로 잘못 가져 간다는 것입니다.

예를 들어 faa.py로 쓸 때 :

from .. import foo

이것은 실행 중에 패키지의 일부로 python에서 faa.py식별하고로드 한 경우에만 의미 가 있습니다. 이 경우, 모듈의 이름
에 대한 faa.py는 예를 들어이 될 것 some_packagename.faa . 파일이 현재 디렉토리에 있기 때문에 파일이로드 된 경우, 파이썬이 실행될 때 파일 이름은 패키지를 참조하지 않으며 결국 상대 가져 오기가 실패합니다.

현재 디렉토리에서 모듈을 참조하는 간단한 해결책은 다음을 사용하는 것입니다.

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo


답변

다음은 예제로 적합하도록 수정 된 일반적인 레시피입니다. 패키지로 작성된 Python 라이브러리를 처리하기 위해 현재 사용하고 있으며, 상호 의존적 인 파일이 포함되어 있으며, 일부는 부분적으로 테스트 할 수 있습니다. 의이 부르 자 lib.foo하고 액세스해야한다고 말하는 lib.fileA기능 f1f2, 및 lib.fileB클래스를 Class3.

print이것이 어떻게 작동하는지 설명하기 위해 몇 가지 전화를 포함 시켰습니다 . 실제로 당신은 그것들을 제거하고 싶을 것 from __future__ import print_function입니다.

이 특정 예제는에 항목을 실제로 삽입해야 할 때 표시하기에는 너무 단순합니다 sys.path. (참조 라스 ‘대답은 우리가 경우에 우리가 패키지 디렉토리의 두 개 이상의 수준을 가지고 있고, 우리가 사용하는 경우, 필요 os.path.dirname(os.path.dirname(__file__))정말하지 않습니다 -하지만 상처 중 여기가.) 그것은없이이 작업을 수행하는 안전 충분한이기도 if _i in sys.path테스트. 그러나 각 가져온 파일을 삽입하는 경우 동일한 경로에 대한 예, 두 경우 fileA와는 fileB패키지-이 클러부터 임포트 유틸리티에 원하는 sys.path그것의 좋은이 가지고있는 동일한 경로 많은 시간이, 그래서 if _i not in sys.path상용구에 있습니다.

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __name__ is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __name__ == '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

여기 아이디어는 이것입니다 (그리고 이것들은 모두 python2.7과 python 3.x에서 동일하게 작동합니다).

  1. 일반 코드에서 import lib또는 from lib import foo일반 패키지로 일반 패키지 가져 오기로 실행하면 __packageis liband __name__is lib.foo입니다. 에서 가져 오는 첫 번째 코드 경로 .fileA를 사용합니다.

  2. 로 실행하면 python lib/foo.py, __package__아무도 없을 것입니다 __name__될 것입니다 __main__.

    우리는 두 번째 코드 경로를 사용합니다. lib디렉토리가 이미있는 것입니다 sys.path추가 할 필요가 없습니다 그래서. fileA등에서 수입

  3. lib디렉토리 내에서 실행될 경우 python foo.py동작은 사례 2와 동일합니다.

  4. lib디렉토리 내에서 실행되는 경우 python -m foo동작은 사례 2 및 3과 유사합니다. 그러나 lib디렉토리 경로 가에 있지 않으므로 sys.path가져 오기 전에 추가합니다. Python을 실행 한 다음에도 마찬가지 import foo입니다.

    이후 ( . sys.path, 우리는 정말 여기에 경로의 절대 버전을 추가 할 필요가 없습니다. 우리가 원하는 깊은 패키지 중첩 구조는 곳입니다 from ..otherlib.fileC import ..., 차이가 있습니다. 당신이이 일을하지 않는 경우, 당신은 할 수 있습니다 모든 sys.path조작을 완전히 생략하십시오 .)

노트

여전히 기발한 것이 있습니다. 이 모든 것을 외부에서 실행하면 :

$ python2 lib.foo

또는:

$ python3 lib.foo

동작은의 내용에 따라 다릅니다 lib/__init__.py. 그것이 존재하고 비어 있으면 모든 것이 잘됩니다.

Package named 'lib'; __name__ is '__main__'

그러나 lib/__init__.py 자체로 가져 routine와서 routine.name직접 (으)로 내보낼 수 있다면 lib.name다음을 얻을 수 있습니다.

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

즉, 패키지를 통해 한 번 모듈을 두 번 가져온 다음 코드를 __main__실행하도록 다시 가져옵니다 main. 파이썬 3.6 이상은 이것에 대해 경고합니다 :

$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'

경고는 새로운이지만, 경고-에 대해 동작하지 않습니다. 일부 는 이중 가져 오기 트랩 이라고 하는 것의 일부 입니다. (자세한 내용은 이슈 27487을 참조하십시오 .) Nick Coghlan의 말 :

이 다음 트랩은 3.3을 포함한 모든 현재 버전의 Python에 존재하며 다음 일반 지침에 요약 할 수 있습니다. “패키지 디렉토리 또는 패키지 내의 디렉토리를 Python 경로에 직접 추가하지 마십시오”.

여기서 규칙을 위반하는 동안 로드되는 파일이 패키지의 일부로로드 되지 않는 경우 에만 해당 규칙이 적용 되며 수정 사항은 해당 패키지의 다른 파일에 액세스 할 수 있도록 특별히 설계되었습니다. (그리고 내가 언급했듯이, 우리는 아마 단일 레벨 패키지에 대해 이것을해서는 안됩니다.) 우리가 깨끗하게하고 싶다면 다음과 같이 다시 작성할 수 있습니다.

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

즉, sys.path가져 오기를 수행 할 수있을 정도로 오래 수정 한 다음 원래 상태로 되돌립니다 ( _iif의 사본 하나만 추가 한 경우에만 사본 하나 삭제 _i).


답변

그래서 다른 많은 사람들과 함께이 문제를 해결 한 후이 기사 에서 Dorian B 가 게시 한 메모를 보았습니다 . 웹 서비스와 함께 사용할 모듈과 클래스를 개발할 때 발생하는 특정 문제를 해결했지만 PyCharm의 디버거 기능을 사용하여 코딩 할 때 테스트 할 수 있습니다. 독립적 인 클래스에서 테스트를 실행하려면 클래스 파일 끝에 다음을 포함시킵니다.

if __name__ == '__main__':
   # run test code here...

그러나 같은 폴더에 다른 클래스 또는 모듈을 가져 오려면 모든 가져 오기 문을 상대 표기법에서 로컬 참조로 변경해야합니다 (예 : 점 (.) 제거). 그러나 Dorian의 제안을 읽은 후 ‘ 하나의 라이너와 작동했습니다! 이제 PyCharm에서 테스트하고 테스트중인 다른 클래스에서 클래스를 사용하거나 웹 서비스에서 사용할 때 테스트 코드를 그대로 둘 수 있습니다!

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

문 검사는 우리가이 모듈을 실행하는 경우 참조하는 경우 또는 그것으로 테스트되고있어 다른 모듈에 사용되고있는 경우 . 아마도 이것은 명백하지만, 위의 상대적인 수입 문제로 좌절 한 다른 사람이 그것을 사용할 수있는 경우에 대비 하여이 메모를 제공합니다.


답변

다음은 권장하지 않지만 모듈이 단순히 생성되지 않은 일부 상황에서 유용 할 수있는 솔루션입니다.

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()


답변

파이썬 모듈 검색 경로를 변경하고 싶지 않고 스크립트에서 모듈을 상대적으로로드 해야하는 비슷한 문제가 발생했습니다 ( “스크립트는 모두를 상대로 가져올 수는 없지만”) BrenBarn이 위에서 설명한 것처럼 ).

그래서 나는 다음과 같은 핵을 사용했습니다. 불행히도, 그것은 imp버전 3.4 이후에 더 이상 사용되지 않는 모듈 에 의존하여 삭제되었습니다 importlib. (이것도 가능 importlib합니까? 모르겠습니다.) 아직도 해킹은 작동합니다.

의 구성원에 액세스하기위한 예 moduleX에서을 subpackage1에 거주하는 스크립트에서 subpackage2폴더 :

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

더 깔끔한 접근법은 Federico가 언급 한대로 모듈을로드하는 데 사용되는 sys.path를 수정하는 것 같습니다.

#!/usr/bin/env python3

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *


답변

__name__ 문제의 코드가 전역 네임 스페이스에서 실행되는지 아니면 가져온 모듈의 일부로 실행되는지에 따라 변경됩니다.

코드가 전역 공간에서 실행되고 있지 않으면 __name__모듈 이름이됩니다. 전역 네임 스페이스에서 실행중인 경우 (예 : 콘솔에 입력하거나 python.exe yourscriptnamehere.py다음을 사용하여 스크립트로 모듈을 실행하는 경우) __name__가됩니다 "__main__".

if __name__ == '__main__'코드가 전역 네임 스페이스에서 실행되고 있는지 테스트하는 데 사용되는 많은 Python 코드 가 있습니다.이를 통해 스크립트의 두 배인 모듈을 가질 수 있습니다.

콘솔에서 가져 오기를 시도 했습니까?