Development Tip

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

yourdevel 2020. 10. 13. 19:27
반응형

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


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

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

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


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`

그런 다음, 당신은 쉽게 가져 오기 문 (예에서와 (다른 파일의 단위) 일정에 액세스 할 수 있습니다 utils.py) from definitions import CONFIG_PATH.


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

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

에서 utils.py우리는 다음과 같은 기능을 정의합니다.

from pathlib import Path

def get_project_root() -> Path:
    """Returns project root folder."""
    return Path(__file__).parent.parent

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

from src.utils import get_project_root

root = get_project_root()

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


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

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

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

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

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

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

이를 달성하는 표준 방법 pkg_resourcessetuptools패키지의 일부인 모듈 을 사용하는 것 입니다. 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_filename경로 러쉬와 얻을 pkg_resources.resource_string러쉬의 내용을 얻는을; 따라서 :

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) 및 현재 작업중인 모듈 내에있는 파일에 액세스 할 수 있다는 것입니다.


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

프로젝트에 로컬 코드

이 예제가 언급되고 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 <module>
    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))

Breaking this down: First we want to accurately find the thread ID of the main thread. In Python3.4+ the threading library has threading.main_thread() however, everybody doesn't use 3.4+ so we search through all threads looking for the main thread save it's ID. If the main thread has already exited, it won't be listed in the threading.enumerate(). We raise a RuntimeError() in this case until I find a better solution.

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")

Next we find the very first stack frame of the main thread. Using the cPython specific function sys._current_frames() we get a dictionary of every thread's current stack frame. Then utilizing inspect.getouterframes() we can retrieve the entire stack for the main thread and the very first frame. current_main_frame = sys._current_frames()[main_id] base_frame = inspect.getouterframes(current_main_frame)[-1] Finally, the differences between Windows and Linux implementations of inspect.getouterframes() need to be handled. Using the cleaned up filename, os.path.abspath() and os.path.dirname() clean things up.

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))

So far I've tested this on Python2.7 and 3.6 on Windows as well as Python3.4 on WSL


Try:

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

This worked for me using a standard PyCharm project with my virtual environment (venv) under the project root directory.

Code below isnt the prettiest, but consistently gets the project root. It returns the full directory path to venv from the VIRTUAL_ENV environment variable e.g. /Users/NAME/documents/PROJECT/venv

It then splits the path at the last /, giving an array with two elements. The first element will be the project path e.g. /Users/NAME/documents/PROJECT

import os

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

I struggled with this problem too until I came to this solution. This is the cleanest solution in my opinion.

In your setup.py add "packages"

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

In your 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')

If you are working with anaconda-project, you can query the PROJECT_ROOT from the environment variable --> os.getenv('PROJECT_ROOT'). This works only if the script is executed via anaconda-project run .

If you do not want your script run by anaconda-project, you can query the absolute path of the executable binary of the Python interpreter you are using and extract the path string up to the envs directory exclusiv. For example: The python interpreter of my conda env is located at:

/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]

This works only with conda-project with fixed project structure of a anaconda-project

참고URL : https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure

반응형