형제 패키지 가져 오기


200

형제 수입품 및 패키지 설명서 에 대한 질문을 읽으려고했지만 아직 답을 찾지 못했습니다.

다음과 같은 구조로 :

├── LICENSE.md
├── README.md
├── api
   ├── __init__.py
   ├── api.py
   └── api_key.py
├── examples
   ├── __init__.py
   ├── example_one.py
   └── example_two.py
└── tests
   ├── __init__.py
   └── test_one.py

examplestests디렉토리 의 스크립트를 api모듈 에서 가져오고 명령 줄에서 어떻게 실행할 수 있습니까?

또한 sys.path.insert모든 파일에 대한 추악한 해킹 을 피하고 싶습니다 . 분명히 이것은 파이썬에서 할 수 있습니다.


7
나는 모든 sys.path해킹을 건너 뛰고 지금까지 게시 된 유일한 실제 솔루션 (7 년 후)을 읽는 것이 좋습니다 .
Aran-Fey 10

1
그건 그렇고, 또 다른 좋은 해결책이 남아 있습니다 : 라이브러리 코드에서 실행 코드를 분리하십시오. 대부분 패키지 내부의 스크립트는 실행 가능 하지 않아야 합니다 .
Aran-Fey

이것은 질문과 답변 모두에 매우 유용합니다. 궁금한 점이 있는데 "승인 된 답변"이이 현상금을 수여 한 사람과 어떻게 다릅니 까?
Indominus

@ Aran-Fey 이것은 상대적 수입 오류 Q & A에서 과소 평가 된 것입니다. 나는이 시간 내내 해킹을 찾고 있었지만, 문제를 해결하는 간단한 방법이 있다는 것을 알았습니다. 그것이 여기있는 모든 사람들을위한 해결책이라고 말할 수는 없지만, 많은 사람들이 할 수있는 좋은 알림입니다.
colorlace

답변:


70

7 년 후

아래 답변을 썼으므로 수정 sys.path은 여전히 ​​개인 스크립트에 잘 작동하는 빠르고 더러운 방법이지만 몇 가지 개선 사항이 있습니다.

  • virtualenv로 패키지를 설치 하면 원하는 것을 얻을 수 있지만 setuptools를 직접 사용하는 대신 pip를 사용 setup.cfg하여 메타 데이터를 저장하는 것이 좋습니다.
  • -m플래그를 사용하고 패키지로 실행해도 작동합니다 (그러나 작업 디렉토리를 설치 가능한 패키지로 변환하려는 경우 약간 어색합니다).
  • 테스트의 경우, 특히 pytest 는이 상황에서 API 패키지를 찾을 수 있으며 sys.path해킹을 처리합니다.

따라서 실제로 원하는 작업에 따라 다릅니다. 그러나 귀하의 목표는 어떤 시점에서 적절한 패키지를 만드는 것이 목표이기 때문에 pip -e아직 완벽하지는 않지만 설치하는 것이 가장 좋습니다.

이전 답변

이미 다른 곳에서 언급했듯이, 형제 모듈에서 가져 오거나 __main__모듈 에서 부모 패키지를 가져 오려면 끔찍한 해킹이 필요 합니다. 이 문제는 PEP 366에 자세히 설명되어 있습니다. PEP 3122 는 수입을보다 합리적인 방식으로 처리하려했으나 귀도는이를 거부했습니다.

유일한 유스 케이스는 모듈의 디렉토리 안에있는 스크립트를 실행하는 것 같습니다. 항상 반 패턴으로 보았습니다.

( 여기 )

하지만이 패턴을 정기적으로 사용하지만

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

path[0]실행중인 스크립트의 상위 폴더와 dir(path[0])최상위 폴더는 다음과 같습니다 .

그래도 여전히 상대적 가져 오기를 사용할 수는 없지만 최상위 레벨 (예 : api상위 폴더) 에서 절대 가져 오기를 허용 합니다.


3
당신이하지 에있는 당신이 경우 사용하여 프로젝트 디렉토리에서 실행 -m패키지 설치하면 양식 또는 (PIP를 쉽게 그것을 만들 VIRTUALENV)
JFS

2
pytest는 API 패키지를 어떻게 찾습니까? pytest 및 형제 패키지 가져 오기에서 특히이 문제가 발생하기 때문에이 스레드를 발견했습니다.
JuniorIncanter

1
두 가지 질문이 있습니다. 1. 당신의 패턴 __package__ = "examples"이 나를 위해 작동하지 않는 것 같습니다 . 왜 사용합니까? 2. 어떤 상황입니다 __name__ == "__main__"__package__하지 None?
actual_panda

@actual_panda Setting __packages__examples.apiiirc 작업 과 같은 절대 경로를 원하고 (마지막으로 한 이후 오랜 시간이 지났음 ) 패키지를 확인하지 않는 경우 도움이됩니다.
Evpok

다른 언어만이 파이썬에서와 동일한 프로세스를 쉽게 만들 수 있다면 어떨까요. 왜 모두가이 언어를 좋아하는지 봅니다. Btw, 문서도 훌륭합니다. 나는 구조화되지 않은 텍스트에서 리턴 유형을 추출하는 것을 좋아합니다 .Javadoc과 phpdoc에서 좋은 변화입니다. ffs ....
매트

168

sys.path 해킹에 지쳤습니까?

sys.path.append사용 가능한 많은 해킹이 있지만 문제를 해결하는 다른 방법을 찾았습니다.

요약

  • (예를 들어 하나 개의 폴더에 코드를 바꿈 packaged_stuff)
  • setuptools.setup ()setup.py 을 사용하는 스크립트 작성을 사용하십시오 .
  • 편집 가능한 상태로 패키지를 핍 설치 pip install -e <myproject_folder>
  • 를 사용하여 가져 오기 from packaged_stuff.modulename import function_name

설정

시작 지점은이라는 폴더에 싸여 제공 한 파일 구조 myproject입니다.

.
└── myproject
    ├── api
       ├── api_key.py
       ├── api.py
       └── __init__.py
    ├── examples
       ├── example_one.py
       ├── example_two.py
       └── __init__.py
    ├── LICENCE.md
    ├── README.md
    └── tests
        ├── __init__.py
        └── test_one.py

.루트 폴더를 호출하고 예제에서는에 위치 C:\tmp\test_imports\합니다.

api.py

테스트 사례로 다음 ./api/api.py를 사용하십시오.

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

test_one을 실행하십시오 :

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\myproject\tests\test_one.py", line 1, in <module>
    from api.api import function_from_api
ModuleNotFoundError: No module named 'api'

또한 상대 수입품을 시도해도 효과가 없습니다.

사용 from ..api.api import function_from_api으로 초래

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\tests\test_one.py", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

단계

  1. setup.py 파일을 루트 레벨 디렉토리에 작성하십시오

의 내용은 setup.py*

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. 가상 환경 사용

가상 환경에 익숙한 경우 가상 환경을 활성화하고 다음 단계로 건너 뜁니다. 가상 환경에서의 사용은하지 않는 절대적으로 필요하지만 그들은 것 정말 장기적으로 당신을 도울 (당신이 진행하는 1 개 이상의 프로젝트가있을 때 ..). 가장 기본적인 단계는 (루트 폴더에서 실행)

  • 가상 환경 생성
    • python -m venv venv
  • 가상 환경 활성화
    • source ./venv/bin/activate(Linux, macOS) 또는 ./venv/Scripts/activate(Win)

이에 대한 자세한 내용은 "python virtual env tutorial"또는 유사 항목을 Google에 알려주십시오. 작성, 활성화 및 비활성화 이외의 다른 명령은 필요하지 않습니다.

가상 환경을 만들고 활성화하면 콘솔은 가상 환경의 이름을 괄호 안에 표시해야합니다

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>

폴더 트리는 다음과 같아야합니다 **

.
├── myproject
   ├── api
      ├── api_key.py
      ├── api.py
      └── __init__.py
   ├── examples
      ├── example_one.py
      ├── example_two.py
      └── __init__.py
   ├── LICENCE.md
   ├── README.md
   └── tests
       ├── __init__.py
       └── test_one.py
├── setup.py
└── venv
    ├── Include
    ├── Lib
    ├── pyvenv.cfg
    └── Scripts [87 entries exceeds filelimit, not opening dir]
  1. pip 프로젝트를 편집 가능한 상태로 설치

myproject사용하여 최상위 패키지 를 설치하십시오 pip. 트릭은 -e설치를 수행 할 때 플래그 를 사용하는 것 입니다. 이렇게하면 편집 가능한 상태로 설치되고 .py 파일에 대한 모든 편집 내용이 설치된 패키지에 자동으로 포함됩니다.

루트 디렉토리에서 다음을 실행하십시오.

pip install -e . (점은 "현재 디렉토리"를 나타냅니다)

또한 다음을 사용하여 설치되었음을 확인할 수 있습니다. pip freeze

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
  1. myproject.수입품에 추가

myproject.그렇지 않으면 작동하지 않는 가져 오기에만 추가해야합니다 . setup.py& 없이 pip install작동 하는 수입품은 여전히 잘 작동합니다. 아래 예를 참조하십시오.


솔루션 테스트

이제 api.py위에서 test_one.py정의하고 아래 정의 된 방법을 사용하여 솔루션을 테스트하겠습니다 .

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

테스트 실행

(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!

* 더 자세한 setup.py 예제 는 setuptools 문서 를 참조하십시오 .

** 실제로, 가상 환경을 하드 디스크의 어느 곳에 나 둘 수 있습니다.


13
자세한 게시물 주셔서 감사합니다. 여기 내 문제가 있습니다. 내가 말한 모든 일을하고 핍 동결 -e git+https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myproject을하면 해결 방법에 대한 아이디어가 있습니까?
Si Mon

2
상대 수입 솔루션이 작동하지 않는 이유는 무엇입니까? 나는 당신을 믿지만 파이썬의 복잡한 시스템을 이해하려고 노력하고 있습니다.
자레드 닐슨

8
누구와 관련하여 문제가 ModuleNotFoundError있습니까? 이 단계에 따라 virtualenv에 'myproject'를 설치했으며 해석 된 세션에 들어가서 실행할 import myprojectModuleNotFoundError: No module named 'myproject'? pip list installed | grep myproject거기 즉, 디렉토리는 정확하고의적인 버전 모두 쇼 pip와는 python정확한 것으로 확인됩니다.
ThatKind

2
헤이 @ NP8, 내가 실수 venv과 운영 체제 :)에 설치, 작동 pip list, 쇼 패키지 동안 pip freeze쇼 이상한 이름 플래그와 함께 설치하는 경우 -e
그르 크루그

3
약 2 시간 동안 상대 수입품을 만드는 방법을 알아 내려고 노력했으며,이 답변은 실제로 현명한 일이었습니다. 👍👍
Graham Lea

43

다음은 tests폴더 의 Python 파일 맨 위에 삽입하는 또 다른 대안입니다 .

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))

1
+1은 정말 간단하고 완벽하게 작동했습니다. 부모 클래스를 가져 오기 (예 : api.api, examples.example_two)에 추가해야하지만 그렇게 선호합니다.
Evan Plaice 2018 년

10
필자는 ..여기에서 테스트와 예제 파일을 포함하는 디렉토리가 아니라 실행중인 디렉토리와 관련이 있다는 것을 초보자와 같이 언급 할 가치가 있다고 생각 합니다. 프로젝트 디렉토리에서 실행 중이며 ./대신 필요 했습니다. 이것이 다른 누군가를 돕기를 바랍니다.
Joshua Detwiler

@JoshDetwiler, 그렇습니다. 나는 그것을 몰랐다. 감사.
doak

1
이것은 나쁜 대답입니다. 경로를 해킹하는 것은 좋은 습관이 아닙니다. 파이썬 세계에서 얼마나 많이 사용되는지는 스캔들입니다. 이 질문의 주요 요점 중 하나는 이런 종류의 해킹을 피하면서 수입을 어떻게 처리 할 수 ​​있는지 보는 것입니다.
jtcotton63

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))@JoshuaDetwiler
vldbnc

31

필요하지 않은 경우가 아니라면 해킹 sys.path하지 않아도됩니다. 사용하다:

import api.api_key # in tests, examples

프로젝트 디렉토리에서 실행하십시오 python -m tests.test_one..

아마도 tests(pi의 단위 테스트 인 경우) 내부로 이동 api하여 실행 python -m api.test하여 모든 테스트 를 실행 __main__.py하거나 (있는 것으로 가정 ) 대신 python -m api.test.test_one실행해야 test_one합니다.

또한 제거 할 수 __init__.py에서 examples(이 파이썬 패키지 없음)과 VIRTUALENV의 예제를 실행하는 api, 예를 들어 설치되어 pip install -e .올바른 위치에 설치할 것 VIRTUALENV에 api당신이 적절한 경우 패키지를 setup.py.


@Alex 대답 은 명시 적으로 " API의 단위 테스트 인 경우 " 라고 표시된 단락을 제외하고 테스트가 API 테스트라고 가정하지 않습니다 .
jfs

불행히도 당신은 루트 디렉토리에서 실행에 갇혀 있고 PyCharm은 여전히 ​​훌륭한 기능을위한 파일을 찾지 못합니다
mhstnsc

@mhstnsc : 정확하지 않습니다. python -m api.test.test_onevirtualenv가 활성화 될 때 어디서나 실행할 수 있어야합니다 . 테스트를 실행하도록 PyCharm을 구성 할 수없는 경우 새 스택 오버 플로우 질문을하십시오 (이 주제에서 기존 질문을 찾을 수없는 경우).
jfs

@ jfs 가상 env 경로를 놓쳤지만 shebang 줄 이상을 사용하여 디렉토리 에서이 물건을 실행하고 싶지 않습니다. PyCharm으로 실행하는 것이 아닙니다. PyCharm을 사용하는 개발자는 완료되었음을 알고 어떤 솔루션에서도 작동하지 않는 기능을 뛰어 넘습니다.
mhstnsc

@mhstnsc 대부분의 경우 적절한 shebang으로 충분하다 (virtualenv python 바이너리를 가리킨다. 괜찮은 파이썬 IDE는 virtualenv를 지원해야한다.
jfs

9

형제 자매 / 상대적인 가져 오기 해킹없이 관련없는 프로젝트간에 코드를 공유하는 의도 된 방법을 보는 데 필요한 Pythonology에 대한 이해력은 아직 없습니다. 그날까지는 이것이 나의 해결책입니다. 들어 examples또는 tests에서 수입 물건 ..\api, 그것과 같을 것이다 :

import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key

이것은 여전히 ​​api 상위 디렉토리를 제공하며 "/ .."연결 sys.path.append (os.path.dirname (os.path.dirname (os.path.abspath ( file )))이 필요하지 않습니다. )
카밀로 산체스

4

형제 패키지 가져 오기 의 경우 [sys.path] [2] 모듈 의 insert 또는 append 메소드를 사용할 수 있습니다 .

if __name__ == '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

다음과 같이 스크립트를 시작하는 경우 작동합니다.

python examples/example_one.py
python tests/test_one.py

반면에 상대적 가져 오기를 사용할 수도 있습니다.

if __name__ == '__main__' and if __package__ is not None:
    import ..api.api

이 경우 '-m'인수 를 사용하여 스크립트를 시작해야합니다 (이 경우 '.py' 확장자 를 지정해서는 안 됨 ).

python -m packageName.examples.example_one
python -m packageName.tests.test_one

물론 두 가지 접근 방식을 혼합하여 스크립트 호출 방식에 관계없이 스크립트가 작동하도록 할 수 있습니다.

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api

__file__세계에 없는 Click 프레임 워크 를 사용하고 있었기 때문에 다음을 사용해야했습니다. sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))그러나 지금은 모든 디렉토리에서 작동합니다
GammaGames

3

TLDR

이 방법에는 setuptools, 경로 해킹, 추가 명령 행 인수 또는 프로젝트의 모든 단일 파일에서 패키지의 최상위 레벨 지정이 필요하지 않습니다.

부모 디렉토리에 스크립트를 작성하여 자신이 원하는 것이 무엇이든간에 __main__모든 것을 실행 하십시오 . 자세한 설명은 계속 읽으십시오.

설명

새 경로를 함께 해킹하거나 추가 명령 줄 인수를 작성하거나 각 프로그램에 코드를 추가하여 형제를 인식하지 않고도 수행 할 수 있습니다.

내가 전에 언급 한 것처럼 이것이 실패하는 이유는 호출되는 프로그램이 __name__로 설정되어 있기 때문 __main__입니다. 이런 상황이 발생하면 호출되는 스크립트는 패키지의 최상위 레벨에있는 것으로 받아들이고 형제 디렉토리의 스크립트 인식을 거부합니다.

그러나, 디렉토리의 최상위에서 모든 것을 여전히 인식 무엇 최고 수준 아래를. 이것은 형제 디렉토리에있는 파일을 서로 인식 / 활용하기 위해해야하는 유일한 것은 부모 디렉토리에있는 스크립트에서 파일을 호출하는 것입니다.

개념 증명 다음 구조의 디렉토리에서 :

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py 다음 코드를 포함합니다 :

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1 / call.py는 다음을 포함합니다 :

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

sib2 / callsib.py는 다음을 포함합니다 :

def CallSib():
    print("Got Called")

if __name__ == '__main__':
    CallSib()

이 예제를 재현하는 경우 해당 호출이 알 Main.py"호출있어"가 발생합니다에 정의 된대로 인쇄되고 sib2/callsib.py 비록이 sib2/callsib.py를 통해 호출되었다 sib1/call.py. 그러나 sib1/call.py가져 오기를 적절하게 변경 한 후 직접 호출 하면 예외가 발생합니다. 부모 디렉토리에서 스크립트에 의해 호출되었을 때 작동했지만 패키지의 최상위 레벨에 있다고 생각되면 작동하지 않습니다.


2

나는 이것을 처리하는 방법을 보여주기 위해 샘플 프로젝트를 만들었습니다. 실제로 위에 표시된 것처럼 다른 sys.path 해킹입니다. 다음에 의존하는 Python Sibling Import Example

if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())

작업 디렉토리가 Python 프로젝트의 루트에 남아있는 한 이것은 매우 효과적입니다. 누구나 실제 프로덕션 환경에 배포하면 작동하는지 잘 들어 보는 것이 좋습니다.


이것은 스크립트의 상위 디렉토리에서 실행중인 경우에만 작동합니다.
Evpok

1

import 문이 관련 코드에서 어떻게 작성되는지 확인해야합니다. examples/example_one.py다음 import 문을 사용하는 경우 :

import api.api

... 그런 다음 프로젝트의 루트 디렉토리가 시스템 경로에 있어야합니다.

해킹없이 이것을 지원하는 가장 쉬운 방법은 다음과 같이 최상위 디렉토리에서 예제를 실행하는 것입니다.

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

Python 2.7.1에서 다음을 얻습니다 $ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api. 나도 마찬가지입니다 import api.api.
zachwill 2016 년

내 대답은 업데이트 ... 당신이 가져 오기 경로, 그 주위에 방법에 현재 디렉토리를 추가해야합니다.
AJ.

1

Eclipse에서 Pydev를 사용하는 누군가가 여기에있는 경우 : 프로젝트-> 속성을 사용 하고 왼쪽 메뉴 Pydev-PYTHONPATH 에서 외부 라이브러리를 설정 하여 형제의 부모 경로 (및 호출 모듈의 부모)를 외부 라이브러리 폴더로 추가 할 수 있습니다 . 그런 다음 형제에서 가져올 수 있습니다 (예 :) .from sibling import some_class


-3

먼저, 모듈 자체와 이름이 같은 파일을 사용하지 않아야합니다. 다른 수입품이 파손될 수 있습니다.

파일을 가져올 때 먼저 인터프리터는 현재 디렉토리를 확인한 후 글로벌 디렉토리를 검색합니다.

내부 examples또는 tests전화 :

from ..api import api

파이썬 2.7.1에서 다음을 얻습니다.Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
zachwill

2
그런 다음 __init__.py최상위 디렉토리에 파일을 추가해야합니다 . 그렇지 않으면 파이썬은 그것을 모듈로 취급 할 수 없습니다

8
작동하지 않습니다. 문제는 부모 폴더가 패키지가 아니라는 __name__것이 __main__아니라 모듈 이 대신하기 package.module때문에 파이썬은 부모 패키지를 볼 수 없으므로 .아무것도 가리 키지 않는다는 것입니다.
Evpok 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.