개발/Python

Python-루트 프로젝트 구조의 경로 가져 오기

MinorMan 2021. 2. 7. 08:51
반응형

<질문>

프로젝트 루트에 구성 파일이있는 python 프로젝트가 있습니다. 프로젝트 전체에 걸쳐 몇 가지 다른 파일에서 구성 파일에 액세스해야합니다.

따라서 다음과 같이 보입니다.<ROOT>/configuration.conf<ROOT>/A/a.py,<ROOT>/A/B/b.py(b, a.py가 구성 파일에 액세스 할 때).

내가 속한 프로젝트 내의 어떤 파일에 의존하지 않고 프로젝트 루트와 구성 파일에 대한 경로를 얻는 가장 / 쉬운 방법은 무엇입니까? 즉 사용하지 않고../../? 프로젝트 루트의 이름을 알고 있다고 가정해도됩니다.


<답변1>

Django가하는 방식으로 이렇게 할 수 있습니다.프로젝트의 최상위 수준에있는 파일에서 프로젝트 루트에 대한 변수를 정의합니다.예를 들어 프로젝트 구조가 다음과 같은 경우 :

project/
    configuration.conf
    definitions.py
    main.py
    utils.py

definitions.py정의 할 수 있습니다 (이렇게하려면import os) :

ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) # This is your Project Root

따라서프로젝트 루트알려진, 당신은 할 수 있습니다구성 위치를 가리키는 변수를 만듭니다.(이것은 어디에서나 정의 할 수 있지만 논리적 위치는 상수가 정의 된 위치에 배치하는 것입니다.definitions.py) :

CONFIG_PATH = os.path.join(ROOT_DIR, 'configuration.conf')  # requires `import os`

그런 다음 import 문을 사용하여 (다른 파일에서) 상수에 쉽게 액세스 할 수 있습니다 (예 :utils.py) :from definitions import CONFIG_PATH.


<답변2>

다른 답변은 프로젝트의 최상위 수준에서 파일을 사용하기위한 조언입니다. 사용하는 경우 필요하지 않습니다.pathlib.Pathparent(Python 3.4 이상). 다음을 제외한 모든 파일이있는 다음 디렉토리 구조를 고려하십시오.README.mdutils.py생략되었습니다.

project
│   README.md
|
└───src
│   │   utils.py
|   |   ...
|   ...

utils.py다음 함수를 정의합니다.

from pathlib import Path

def get_project_root() -> Path:
    return Path(__file__).parent.parent

프로젝트의 모든 모듈에서 다음과 같이 프로젝트 루트를 가져올 수 있습니다.

from src.utils import get_project_root

root = get_project_root()

혜택: 호출하는 모든 모듈get_project_root프로그램 동작을 변경하지 않고 이동할 수 있습니다. 모듈이utils.py업데이트해야합니다.get_project_root가져 오기 (리팩토링 도구를 사용하여이를 자동화 할 수 있음).


<답변3>

이전의 모든 솔루션은 내가 필요하다고 생각하는 것에 대해 지나치게 복잡해 보였으며 종종 저에게 적합하지 않았습니다. 다음 한 줄 명령은 원하는 작업을 수행합니다.

import os
ROOT_DIR = os.path.abspath(os.curdir)

<답변4>

"루트"모듈의 경로를 얻으려면 다음을 사용할 수 있습니다.

import os
import sys
os.path.dirname(sys.modules['__main__'].__file__)

그러나 더 흥미롭게도 최상위 모듈에 구성 "객체"가있는 경우 다음과 같이 읽을 수 있습니다.

app = sys.modules['__main__']
stuff = app.config.somefunc()

<답변5>

이를 달성하는 표준 방법은pkg_resources의 일부인 모듈setuptools꾸러미.setuptools설치 가능한 파이썬 패키지를 만드는 데 사용됩니다.

당신이 사용할 수있는pkg_resources원하는 파일의 내용을 문자열로 반환하고 다음을 사용할 수 있습니다.pkg_resources시스템에서 원하는 파일의 실제 경로를 가져옵니다.

라는 패키지가 있다고 가정 해 보겠습니다.stackoverflow.

stackoverflow/
|-- app
|   `-- __init__.py
`-- resources
    |-- bands
    |   |-- Dream\ Theater
    |   |-- __init__.py
    |   |-- King's\ X
    |   |-- Megadeth
    |   `-- Rush
    `-- __init__.py

3 directories, 7 files

이제 모듈에서 Rush 파일에 액세스하려고한다고 가정 해 보겠습니다.app.run. 사용하다pkg_resources.resouces_filenameRush로가는 길을 찾고pkg_resources.resource_stringRush의 내용을 얻으려면; 따라서 :

import pkg_resources

if __name__ == "__main__":
    print pkg_resources.resource_filename('resources.bands', 'Rush')
    print pkg_resources.resource_string('resources.bands', 'Rush')

출력 :

/home/sri/workspace/stackoverflow/resources/bands/Rush
Base: Geddy Lee
Vocals: Geddy Lee
Guitar: Alex Lifeson
Drums: Neil Peart

이것은 파이썬 경로의 모든 패키지에서 작동합니다. 그래서 당신이 어디에 있는지 알고 싶다면lxml.etree시스템에 있습니다.

import pkg_resources

if __name__ == "__main__":
    print pkg_resources.resource_filename('lxml', 'etree')

산출:

/usr/lib64/python2.7/site-packages/lxml/etree

요점은이 표준 방법을 사용하여 시스템에 설치된 파일 (예 : pip install xxx 또는 yum -y install python-xxx)과 현재 작업중인 모듈 내에있는 파일에 액세스 할 수 있다는 것입니다.


<답변6>

아래 코드는 프로젝트 루트까지 경로를 반환합니다.

import sys
print(sys.path[1])

<답변7>

시험:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

<답변8>

최근에 비슷한 작업을 시도해 왔으며 이러한 답변이 내 사용 사례 (프로젝트 루트를 감지해야하는 분산 라이브러리)에 적합하지 않다는 것을 발견했습니다. 주로 저는 다른 환경과 플랫폼과 싸우고 있지만 여전히 완벽하게 보편적 인 것을 찾지 못했습니다.

이 예제가 언급되고 Django 등 몇 군데에서 사용되는 것을 보았습니다.

import os
print(os.path.dirname(os.path.abspath(__file__)))

간단하지만, 스 니펫이있는 파일이 실제로 프로젝트의 일부인 경우에만 작동합니다.우리는 프로젝트 디렉토리를 검색하지 않고 대신 스 니펫의 디렉토리를 검색합니다.

마찬가지로sys.modules접근은 때 무너진다호출응용 프로그램의 진입 점 외부에서 특히 하위 스레드가 '본관'모듈. 자식 스레드에서 가져 오기를 보여주기 위해 함수 내부에 가져 오기를 명시 적으로 넣었습니다. app.py의 최상위 수준으로 이동하면 문제가 해결됩니다.

app/
|-- config
|   `-- __init__.py
|   `-- settings.py
`-- app.py

app.py

#!/usr/bin/env python
import threading


def background_setup():
    # Explicitly importing this from the context of the child thread
    from config import settings
    print(settings.ROOT_DIR)


# Spawn a thread to background preparation tasks
t = threading.Thread(target=background_setup)
t.start()

# Do other things during initialization

t.join()

# Ready to take traffic

settings.py

import os
import sys


ROOT_DIR = None


def setup():
    global ROOT_DIR
    ROOT_DIR = os.path.dirname(sys.modules['__main__'].__file__)
    # Do something slow

이 프로그램을 실행하면 속성 오류가 발생합니다.

>>> import main
>>> Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python2714\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python2714\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "main.py", line 6, in background_setup
    from config import settings
  File "config\settings.py", line 34, in 
    ROOT_DIR = get_root()
  File "config\settings.py", line 31, in get_root
    return os.path.dirname(sys.modules['__main__'].__file__)
AttributeError: 'module' object has no attribute '__file__'

... 따라서 스레딩 기반 솔루션

이전과 동일한 애플리케이션 구조를 사용하지만 settings.py 수정

import os
import sys
import inspect
import platform
import threading


ROOT_DIR = None


def setup():
    main_id = None
    for t in threading.enumerate():
        if t.name == 'MainThread':
            main_id = t.ident
            break

    if not main_id:
        raise RuntimeError("Main thread exited before execution")

    current_main_frame = sys._current_frames()[main_id]
    base_frame = inspect.getouterframes(current_main_frame)[-1]

    if platform.system() == 'Windows':
        filename = base_frame.filename
    else:
        filename = base_frame[0].f_code.co_filename

    global ROOT_DIR
    ROOT_DIR = os.path.dirname(os.path.abspath(filename))

세분화 : 먼저 메인 스레드의 스레드 ID를 정확하게 찾고 싶습니다. Python3.4 +에서 스레딩 라이브러리에는threading.main_thread()그러나 모두가 3.4 이상을 사용하지 않으므로 모든 스레드를 검색하여 주 스레드를 찾고 ID를 저장합니다. 메인 스레드가 이미 종료 된 경우에는 목록에 표시되지 않습니다.threading.enumerate(). 우리는RuntimeError()이 경우 더 나은 해결책을 찾을 때까지.

main_id = None
for t in threading.enumerate():
    if t.name == 'MainThread':
        main_id = t.ident
        break

if not main_id:
    raise RuntimeError("Main thread exited before execution")

다음으로 메인 스레드의 첫 번째 스택 프레임을 찾습니다.cPython 특정 기능 사용sys._current_frames()모든 스레드의 현재 스택 프레임에 대한 사전을 얻습니다. 그런 다음 활용inspect.getouterframes()메인 스레드와 첫 번째 프레임에 대한 전체 스택을 검색 할 수 있습니다. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [-1] 마지막으로 Windows 및 Linux 구현 간의 차이점inspect.getouterframes()처리해야합니다. 정리 된 파일 이름을 사용하여os.path.abspath()os.path.dirname()물건을 정리하십시오.

if platform.system() == 'Windows':
    filename = base_frame.filename
else:
    filename = base_frame[0].f_code.co_filename

global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))

지금까지 Windows의 Python2.7 및 3.6과 WSL의 Python3.4에서 이것을 테스트했습니다.


<답변9>

이 솔루션에 도달 할 때까지이 문제로 어려움을 겪었습니다. 이것은 제 생각에 가장 깨끗한 해결책입니다.

당신의setup.py"패키지"추가

setup(
name='package_name'
version='0.0.1'
.
.
.
packages=['package_name']
.
.
.
)

당신의python_script.py

import pkg_resources
import os

resource_package = pkg_resources.get_distribution(
    'package_name').location
config_path = os.path.join(resource_package,'configuration.conf')

<답변10>

예 : I want to runrunio.py안으로부터helper1.py

프로젝트 트리 예 :

myproject_root
- modules_dir/helpers_dir/helper1.py
- tools_dir/runio.py

프로젝트 루트 가져 오기 :

import os
rootdir = os.path.dirname(os.path.realpath(__file__)).rsplit(os.sep, 2)[0]

스크립트에 대한 빌드 경로 :

runme = os.path.join(rootdir, "tools_dir", "runio.py")
execfile(runme)

<답변11>

이것은 프로젝트 루트 디렉토리 아래의 가상 환경 (venv)과 함께 표준 PyCharm 프로젝트를 사용하여 저에게 효과적이었습니다.

아래 코드는 가장 예쁘지는 않지만 지속적으로 프로젝트 루트를 얻습니다. venv에 대한 전체 디렉토리 경로를VIRTUAL_ENV환경 변수 예/Users/NAME/documents/PROJECT/venv

그런 다음 마지막에 경로를 분할합니다./, 두 개의 요소가있는 배열을 제공합니다. 첫 번째 요소는 프로젝트 경로입니다./Users/NAME/documents/PROJECT

import os

print(os.path.split(os.environ['VIRTUAL_ENV'])[0])

<답변12>

../ 메서드를 사용하여 현재 프로젝트 경로를 가져 왔습니다.

예 : Project1-D : \ projects

src

구성 파일

Configuration.cfg

Path = "../ src / ConfigurationFiles / Configuration.cfg"


<답변13>

생각만큼 간단하지 않기 때문에 맞춤형 솔루션을 구현해야했습니다. 내 솔루션은 스택 추적 검사 (inspect.stack()) +sys.path함수가 호출 된 python 모듈의 위치 나 인터프리터 (시 쉘 및 기타에서 PyCharm에서 실행하여 시도했습니다. 다음은 주석이있는 전체 구현입니다.

def get_project_root_dir() -> str:
    """
    Returns the name of the project root directory.

    :return: Project root directory name
    """

    # stack trace history related to the call of this function
    frame_stack: [FrameInfo] = inspect.stack()

    # get info about the module that has invoked this function
    # (index=0 is always this very module, index=1 is fine as long this function is not called by some other
    # function in this module)
    frame_info: FrameInfo = frame_stack[1]

    # if there are multiple calls in the stacktrace of this very module, we have to skip those and take the first
    # one which comes from another module
    if frame_info.filename == __file__:
        for frame in frame_stack:
            if frame.filename != __file__:
                frame_info = frame
                break

    # path of the module that has invoked this function
    caller_path: str = frame_info.filename

    # absolute path of the of the module that has invoked this function
    caller_absolute_path: str = os.path.abspath(caller_path)

    # get the top most directory path which contains the invoker module
    paths: [str] = [p for p in sys.path if p in caller_absolute_path]
    paths.sort(key=lambda p: len(p))
    caller_root_path: str = paths[0]

    if not os.path.isabs(caller_path):
        # file name of the invoker module (eg: "mymodule.py")
        caller_module_name: str = Path(caller_path).name

        # this piece represents a subpath in the project directory
        # (eg. if the root folder is "myproject" and this function has ben called from myproject/foo/bar/mymodule.py
        # this will be "foo/bar")
        project_related_folders: str = caller_path.replace(os.sep + caller_module_name, '')

        # fix root path by removing the undesired subpath
        caller_root_path = caller_root_path.replace(project_related_folders, '')

    dir_name: str = Path(caller_root_path).name

    return dir_name

<답변14>

anaconda-project로 작업하는 경우 환경 변수-> os.getenv ( 'PROJECT_ROOT')에서 PROJECT_ROOT를 쿼리 할 수 있습니다. 이것은 스크립트가 anaconda-project run을 통해 실행되는 경우에만 작동합니다.

anaconda-project에서 스크립트를 실행하지 않으려면 사용중인 Python 인터프리터의 실행 가능한 바이너리의 절대 경로를 쿼리하고 envs 디렉터리 exclusiv까지 경로 문자열을 추출 할 수 있습니다. 예 : 내 conda 환경의 파이썬 인터프리터는 다음 위치에 있습니다.

/ home / user / project_root / envs / default / bin / python

# You can first retrieve the env variable PROJECT_DIR.
# If not set, get the python interpreter location and strip off the string till envs inclusiv...

if os.getenv('PROJECT_DIR'):
    PROJECT_DIR = os.getenv('PROJECT_DIR')
else:
    PYTHON_PATH = sys.executable
    path_rem = os.path.join('envs', 'default', 'bin', 'python')
    PROJECT_DIR = py_path.split(path_rem)[0]

이것은 anaconda-project의 고정 된 프로젝트 구조를 가진 conda-project에서만 작동합니다.


<답변15>

글을 쓰는 시점에서 다른 솔루션은 매우 독립적 인 솔루션이 없습니다. 환경 변수 또는 패키지 구조의 모듈 위치에 따라 다릅니다. 'Django'솔루션의 최상위 답변은 상대적인 가져 오기를 요구하여 후자의 희생양이됩니다. 또한 최상위 수준에서 모듈을 수정해야하는 단점도 있습니다.

이것은 최상위 패키지의 디렉토리 경로를 찾는 올바른 방법이어야합니다.

import sys
import os

root_name, _, _ = __name__.partition('.')
root_module = sys.modules[root_name]
root_dir = os.path.dirname(root_module.__file__)

config_path = os.path.join(root_dir, 'configuration.conf')

에 포함 된 점선 문자열의 첫 번째 구성 요소를 가져 와서 작동합니다.__name__그리고 그것을 키로 사용sys.modules최상위 패키지의 모듈 객체를 반환합니다. 이것의__file__속성은 트리밍 후 원하는 경로를 포함합니다./__init__.py사용os.path.dirname().

이 솔루션은 독립적입니다. 최상위 수준을 포함하여 패키지의 모든 모듈에서 작동합니다.__init__.py파일.


<답변16>

나는 다음과 같이 스스로 결정했다.
기본 파일에서 'MyProject / drivers'의 경로를 가져와야합니다.

MyProject/
├─── RootPackge/
│    ├── __init__.py
│    ├── main.py
│    └── definitions.py
│
├─── drivers/
│    └── geckodriver.exe
│
├── requirements.txt
└── setup.py

definitions.py
프로젝트의 루트가 아니라 기본 패키지의 루트에 넣습니다.

from pathlib import Path

ROOT_DIR = Path(__file__).parent.parent

ROOT_DIR 사용 :
main.py

# imports must be relative,
# not from the root of the project,
# but from the root of the main package.
# Not this way:
# from RootPackge.definitions import ROOT_DIR
# But like this:
from definitions import ROOT_DIR

# Here we use ROOT_DIR
# get path to MyProject/drivers
drivers_dir = ROOT_DIR / 'drivers'
# Thus, you can get the path to any directory
# or file from the project root

driver = webdriver.Firefox(drivers_dir)
driver.get('http://www.google.com')

그러면 PYTHON_PATH가 'definitions.py'파일에 액세스하는 데 사용되지 않습니다.

PyCharm에서 작동 :
'main.py'파일 실행 (Windows에서는 Ctrl + Shift + F10)

프로젝트 루트의 CLI에서 작동합니다.

$ py RootPackge/main.py

RootPackge의 CLI에서 작동합니다.

$ cd RootPackge
$ py main.py

프로젝트 위의 디렉토리에서 작동 :

$ cd ../../../../
$ py MyWork/PythoProjects/MyProject/RootPackge/main.py

주 파일에 대한 절대 경로를 제공하면 어디서나 작동합니다.
venv에 의존하지 않습니다.


<답변17>

여기에 많은 답변이 있지만 모든 경우를 포괄하는 간단한 것을 찾을 수 없으므로 솔루션을 제안 할 수도 있습니다.

 import pathlib import os def get_project_root(): """ There is no way in python to get project root. This function uses a trick. We know that the function that is currently running is in the project. We know that the root project path is in the list of PYTHONPATH look for any path in PYTHONPATH list that is contained in this function's path Lastly we filter and take the shortest path because we are looking for the root. :return: path to project root """ apth = str(pathlib.Path().absolute()) ppth = os.environ['PYTHONPATH'].split(':') matches = [x for x in ppth if x in apth] project_root = min(matches, key=len) return project_root

반응형