Programming

형제 패키지 가져 오기

procodes 2020. 6. 23. 08:04
반응형

형제 패키지 가져 오기


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

다음과 같은 구조로 :

├── 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은 여전히 ​​개인 스크립트에 잘 작동하는 빠르고 더러운 트릭이지만 몇 가지 개선 사항이 있습니다.

  • 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상위 폴더) 에서 절대 가져 오기를 허용 합니다.


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

sys.path.append사용 가능한 많은 해킹이 있지만 문제를 해결하는 다른 방법 인 setuptools를 찾았습니다 . 이것으로 잘 작동하지 않는 에지 케이스가 있는지 확실하지 않습니다. 다음은 Python 3.6.5, (Anaconda, conda 4.5.1), Windows 10 컴퓨터에서 테스트되었습니다.


설정

시작 지점은이라는 폴더에 싸여 제공 한 파일 구조 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())

2) 가상 환경 사용

가상 환경에 익숙한 경우 가상 환경을 활성화하고 다음 단계로 건너 뜁니다. 가상 환경에서의 사용은하지 않는 절대적으로 필요하지만 그들은 것 정말 장기적으로 당신을 도울 (당신이 진행하는 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]

3) 프로젝트를 편집 가능한 상태로 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

4) myproject.수입품에 추가 하십시오

Note that you will have to add myproject. only into imports that would not work otherwise. Imports that worked without the setup.py & pip install will work still work fine. See an example below.


Test the solution

Now, let's test the solution using api.py defined above, and test_one.py defined below.

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

running the test

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

* See the setuptools docs for more verbose setup.py examples.

** In reality, you could put your virtual environment anywhere on your hard disk.


Here is another alternative that I insert at top of the Python files in tests folder:

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

You don't need and shouldn't hack sys.path unless it is necessary and in this case it is not. Use:

import api.api_key # in tests, examples

Run from the project directory: python -m tests.test_one.

You should probably move tests (if they are api's unittests) inside api and run python -m api.test to run all tests (assuming there is __main__.py) or python -m api.test.test_one to run test_one instead.

You could also remove __init__.py from examples (it is not a Python package) and run the examples in a virtualenv where api is installed e.g., pip install -e . in a virtualenv would install inplace api package if you have proper setup.py.


I don't yet have the comprehension of Pythonology necessary to see the intended way of sharing code amongst unrelated projects without a sibling/relative import hack. Until that day, this is my solution. For examples or tests to import stuff from ..\api, it would look like:

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

For siblings package imports, you can use either the insert or the append method of the [sys.path][2] module:

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

This will work if you are launching your scripts as follows:

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

On the other hand, you can also use the relative import:

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

In this case you will have to launch your script with the '-m' argument (note that, in this case, you must not give the '.py' extension):

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

Of course, you can mix the two approaches, so that your script will work no matter how it is called:

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

TLDR

This method does not require setuptools, path hacks, additional command line arguments, or specifying the top level of the package in every single file of your project.

Just make a script in the parent directory of whatever your are calling to be your __main__ and run everything from there. For further explanation continue reading.

Explanation

This can be accomplished without hacking a new path together, extra command line args, or adding code to each of your programs to recognize its siblings.

The reason this fails as I believe was mentioned before is the programs being called have their __name__ set as __main__. When this occurs the script being called accepts itself to be on the top level of the package and refuses to recognize scripts in sibling directories.

However, everything under the top level of the directory will still recognize ANYTHING ELSE under the top level. This means the ONLY thing you have to do to get files in sibling directories to recognize/utilize each other is to call them from a script in their parent directory.

Proof of Concept In a dir with the following structure:

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

Main.py contains the following code:

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1/call.py contains:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

and sib2/callsib.py contains:

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

if __name__ == '__main__':
    CallSib()

If you reproduce this example you will notice that calling Main.py will result in "Got Called" being printed as is defined in sib2/callsib.py even though sib2/callsib.py got called through sib1/call.py. However if one were to directly call sib1/call.py (after making appropriate changes to the imports) it throws an exception. Even though it worked when called by the script in its parent directory, it will not work if it believes itself to be on the top level of the package.


You need to look to see how the import statements are written in the related code. If examples/example_one.py uses the following import statement:

import api.api

...then it expects the root directory of the project to be in the system path.

The easiest way to support this without any hacks (as you put it) would be to run the examples from the top level directory, like this:

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

Just in case someone using Pydev on Eclipse end up here: you can add the sibling's parent path (and thus the calling module's parent) as an external library folder using Project->Properties and setting External Libraries under the left menu Pydev-PYTHONPATH. Then you can import from your sibling, e. g. from sibling import some_class.


I made a sample project to demonstrate how I handled this, which is indeed another sys.path hack as indicated above. Python Sibling Import Example, which relies on:

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

This seems to be pretty effective so long as your working directory remains at the root of the Python project. If anyone deploys this in a real production environment it'd be great to hear if it works there as well.


First, you should avoid having files with the same name as the module itself. It may break other imports.

When you import a file, first the interpreter checks the current directory and then searchs global directories.

Inside examples or tests you can call:

from ..api import api

참고URL : https://stackoverflow.com/questions/6323860/sibling-package-imports

반응형