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


127

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

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

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


않는 <ROOT>/__init__.py존재 하는가?
mgilson

구성 파일이 python 모듈이고 import 문으로 쉽게 액세스 할 수 있습니다.이 파일은 python 모듈이 아니므로 잘 알려진 위치에 배치해야합니다. 예를 들면 $ HOME / .my_project / my_project.conf입니다.
John Smith 선택 사항

@JohnSmithOptional-JSON 파일입니다. 경로를 사용하여 액세스 할 수 있어야합니다. 예. 모든 폴더에 포함됩니다.
Shookie

_ 프로젝트 루트의 이름을 알고 있다고 가정해도 괜찮습니다 ._ 프로젝트 경로를 알고 있다는 뜻입니까? 그럼 그냥 os.path.join (known_root_name, "configuration.conf") 아닌가요?
tdelaney

사용자 구성 인 경우 일반적으로 os.path.expanduser('~/.myproject/myproject.conf'). Unix 및 Windows에서 작동합니다.
John Smith 선택 사항

답변:


157

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.


1
이와 같은 definitions.py 파일을 포함하려면 __init__.py루트 프로젝트 디렉토리에도 파일을 추가해야 합니까? 정확해야합니까? 저는 방금 파이썬으로 시작했고 모범 사례에 대해 확신하지 못했습니다. 감사.
akskap

3
@akskap : 아니요, __init__.py이 파일은 패키지를 정의 할 때만 필요하기 때문에 필요하지 않습니다 . __init__.py파일은 Python이 디렉터리를 패키지를 포함하는 것으로 취급하도록 만드는 데 필요합니다. 이는 문자열과 같은 공통 이름을 가진 디렉토리가 나중에 모듈 검색 경로에서 발생하는 유효한 모듈을 의도하지 않게 숨기는 것을 방지하기 위해 수행됩니다. 가장 간단한 경우 __init__.py에는 빈 파일 일 수도 있지만 __all__나중에 설명 할 패키지에 대한 초기화 코드를 실행하거나 변수를 설정할 수도 있습니다. 참조 : docs.python.org/3/tutorial/modules.html#packages
jrd1

나는 이러한 정의를 __init.py__루트 패키지 에 추가하는 것이 수용 가능하거나 눈살을 찌푸리는 것이 호기심이 많고 스타일이 현명 합니다. 더 좋은 구문을 허용 할뿐만 아니라 다른 파일을 만드는 것을 절약 할 수 from root_pack import ROOT_DIR, CONFIG_PATH있습니다.
Johndt6

@ Johndt6 : 관례는 __init__.py비워 두는 것이지만 그것은 엄격히 사실이 아닙니다 (결국 관례입니다). 자세한 내용은 다음을 참조하십시오. stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor : 아니오-인용 한 예 os.path.abspath에서 문자열 '__file__'. 리콜 __file__실제로 파이썬 모듈에 정의되어 가져 오기 속성입니다. 이 경우 __file__모듈이로드 된 경로 이름을 반환합니다. 자세한 내용은 여기 (모듈 섹션 참조) : docs.python.org/3/reference/datamodel.html
jrd1

62

다른 답변은 프로젝트의 최상위 수준에서 파일을 사용하기위한 조언입니다. pathlib.Pathparent(Python 3.4 이상) 을 사용하는 경우에는 필요하지 않습니다 . README.md및 제외 된 모든 파일 utils.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하고 가져 오기를 수행해야합니다 (리팩터링 도구를 사용하여 자동화 할 수 있음).


2
루트에있는 모든 모듈. 루트 외부에서 src.utils를 호출하면 작동하지 않습니다. 내가 잘못?
aerijman

이름 ' file '이 정의되지 않았습니다. 이유가 무엇입니까?
Luk Aron

26

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

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

3
디렉토리 루트의 config.py에 .. bamn! 당신은 싱글 톤을 얻지 못했습니다.
swdev

2
이 방법은 존재하는 경로 내에서 응용 프로그램을 실행한다고 가정합니다. 많은 "사용자"는 데스크탑에서 클릭하는 아이콘을 가지고 있거나 완전히 다른 디렉토리에서 앱을 실행할 수 있습니다.
DevPlayer

23

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

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

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

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

1
여기 os에서는 기본적으로 사용할 수 없습니다. 을 가져와야 os합니다. 따라서 줄을 추가하면 import os답이 더 완전 해집니다.
Md. Abu Nafee Ibna Zahid

5
이것은 실행 된 스크립트를 포함하는 디렉토리를 제공합니다. 실행 예를 들어, python3 -m topmodule.submodule.script그것은 줄 것이다 /path/to/topmodule/submodule대신 /path/to/topmodule.
danijar

14

이를 달성하는 표준 방법 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) 및 현재 작업중인 모듈 내에있는 파일에 액세스 할 수 있다는 것입니다.


1
나는 당신의 밴드 선택을 좋아한다!
dylan_fan


3

시험:

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

1
이것이 내가 필요한 것입니다. 내 구조가 root-> config-> conf.py 였기 때문에 간단한 솔루션이 저에게 효과적입니다. conf.py에서 프로젝트 루트를 정의하고 싶었고 루트는 해당 파일에서 정확히 두 단계 위였습니다.
Daniyal Arshad

2

이 솔루션에 도달 할 때까지이 문제로 고생했습니다. 이것은 제 생각에 가장 깨끗한 해결책입니다.

당신의에서 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')

가상 환경을 사용하고 패키지를 설치하는 python3 setup.py install것은 더 이상 소스 코드 폴더를 가리키는 것이 아니라 ~./virtualenv/..../app.egg. 그래서 구성 파일을 패키지 설치에 포함시켜야했습니다.
loxosceles

2

예 : helper1.py 내에서 runio.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)

1

이것은 프로젝트 루트 디렉토리 아래의 가상 환경 (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])

3
anaconda 또는 pipenv와 같은 설정에서는 작동하지 않습니다. 이러한 경우에는 가상 환경이 프로젝트에 포함되지 않기 때문입니다.
Gripp

1

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

프로젝트에 로컬 코드

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

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

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에서 이것을 테스트했습니다.


0

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에서만 작동합니다.


0

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

예 : Project1-D : \ projects

src

구성 파일

Configuration.cfg

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


0

글을 쓰는 시점에서 다른 솔루션은 매우 독립적 인 솔루션이 없습니다. 환경 변수 또는 패키지 구조에서 모듈의 위치에 따라 다릅니다. '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파일을 포함하여 패키지의 모든 모듈에서 작동 합니다.


솔루션에 대한 간단한 설명과 솔루션으로 사용할 수있는 방법을 추가 할 수 있습니까?
LuRsT

0

생각만큼 간단하지 않기 때문에 맞춤형 솔루션을 구현해야했습니다. 내 솔루션은 스택 추적 검사 ( inspect.stack()) +를 기반으로 sys.path하며 함수가 호출 된 파이썬 모듈의 위치 나 인터프리터 (시 셸 및 기타에서 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

-1

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.