Python-ルートプロジェクト構造のパスを取得する


127

プロジェクトルートに構成ファイルを含むpythonプロジェクトがあります。構成ファイルは、プロジェクト全体でいくつかの異なるファイルでアクセスする必要があります。

したがって、次のようになります<ROOT>/configuration.conf <ROOT>/A/a.py<ROOT>/A/B/b.py(B、a.pyアクセス設定ファイルを)。

私がいるプロジェクト内のファイルに依存せずに、プロジェクトルートと構成ファイルへのパスを取得するための最良/最も簡単な方法は何ですか?すなわち使用せずに../../?プロジェクトルートの名前がわかっていると想定しても問題ありません。


ない<ROOT>/__init__.py存在?
mgilson 2014

構成ファイルはpythonモジュールであり、importステートメントを使用するだけで簡単にアクセスできます。これはpythonモジュールではないため、よく知られた場所に配置する必要があります。たとえば、$ HOME / .my_project / my_project.confです。
John Smithオプション

@JohnSmithOptional-JSONファイルです。パスを使用してアクセスできるようにする必要があります。はい。すべてのフォルダに含まれています。
Shookie 2014

_プロジェクトのルート名がわかっていると仮定しても問題ありません。_それは、プロジェクトへのパスがわかっているという意味ですか?それは単にos.path.join(known_root_name、 "configuration.conf")ではないですか?
tdelaney 2014

ユーザー設定の場合、通常はのようなものを使用しますos.path.expanduser('~/.myproject/myproject.conf')。UnixとWindowsで動作します。
ジョンスミスオプション

回答:


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、ルートプロジェクトディレクトリにもファイルを追加する必要がありますか?正しいでしょうか?私はpythonを始めたばかりで、ベストプラクティスについて確信が持てません。ありがとう。
akskap

3
@akskap:いいえ、__init__.pyパッケージを定義するときに、そのファイルが唯一必要とされるように、必要とされることはありません。ファイルはPythonがパッケージを含むようディレクトリを扱うために必要されています。これは、文字列などの一般的な名前のディレクトリが、後でモジュール検索パスで発生する有効なモジュールを誤って非表示にしないようにするために行われます。最も単純なケースでは、空のファイルにすることができますが、パッケージの初期化コードを実行したり、後で説明する変数を設定したりすることもできます 参照:docs.python.org/3/tutorial/modules.html#packages__init__.py__init__.py__all__
jrd1

これらの定義を__init.py__ルートパッケージのに追加することが受け入れられるか、それとも眉をひそめるかに関わらず、私はスタイルに興味があります。別のファイルを作成する手間を省くだけでなく、より適切な構文を使用できますfrom root_pack import ROOT_DIR, CONFIG_PATH
Johndt6 2017年

@ Johndt6:規則は__init__.py空のままにすることですが、厳密には真ではありません(結局のところ規則です)。詳細については、こちらをご覧ください:stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor:いいえ-引用した例でos.path.abspathは、文字列を呼び出しています'__file__'。これ__file__は、実際にはPythonモジュールに対して定義されているインポート属性であることを思い出してください。この場合、__file__はモジュールのロード元のパス名を返します。詳細については、こちら(モジュールのセクションを参照):docs.python.org/3/reference/datamodel.html
jrd1

61

他の回答は、プロジェクトのトップレベルのファイルを使用するためのアドバイスです。pathlib.Pathand parent(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

名前「ファイル」が定義されていません、なぜですか?
Luk Aron

26

以前のソリューションはすべて、私が必要と考えるものに対して複雑すぎるようであり、多くの場合、私にはうまくいきませんでした。次の1行のコマンドは、必要な処理を実行します。

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

3
それをconfig.pyのディレクトリのルートに配置します。あなたはシングルトンを手に入れました。
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ます。
ダニジャー

14

これを実現する標準的な方法pkg_resourcesは、setuptoolsパッケージの一部であるモジュールを使用することです。 setuptoolsインストール可能なPythonパッケージを作成するために使用されます。

を使用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.runpkg_resources.resouces_filenameRushへのパスを取得し、Rush 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

これは、Pythonパス内のすべてのパッケージで機能します。したがって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で定義したかったのですが、ルートはそのファイルからちょうど2レベル上にありました。
Arshad

3

以下のコードは、プロジェクトのルートまでのパスを返します

import sys
print(sys.path[1])

いいヒント!なぜ誰もあなたの答えに賛成しなかったのかしら:P
daveoncode

ありがとうDaveon本当に感謝しています!!
Arpan Saini

残念ながら、そうではありません。単純です:P ...私の完全なソリューションを見てください:stackoverflow.com/a/62510836/267719
daveoncode

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

ただ、例:私が実行したいrunio.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)

1

これは、プロジェクトのルートディレクトリの下の仮想環境(venv)で標準のPyCharmプロジェクトを使用して私に役立ちました。

以下のコードは最もきれいではありませんが、常にプロジェクトのルートを取得します。VIRTUAL_ENV環境変数からvenvへの完全なディレクトリパスを返します。/Users/NAME/documents/PROJECT/venv

次に、パスを最後に分割し、/2つの要素を持つ配列を提供します。最初の要素はプロジェクトのパスになります。/Users/NAME/documents/PROJECT

import os

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

3
これらの場合、仮想環境はプロジェクト内に含まれていないため、anacondaやpipenvなどのセットアップでは機能しません。
グリップ

1

私は最近同様のことをしようと試みており、これらの回答は私のユースケース(プロジェクトのルートを検出する必要がある分散ライブラリ)には不十分であることがわかりました。主に私はさまざまな環境やプラットフォームと戦っていますが、それでも完全に普遍的なものは見つかりませんでした。

プロジェクトにローカルなコード

この例がDjangoなどのいくつかの場所で言及され、使用されているのを見てきました。

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

これは単純ですが、スニペットが含まれているファイルが実際にプロジェクトの一部である場合にのみ機能します。プロジェクトディレクトリを取得するのではなく、スニペットのディレクトリを取得します

同様に、sys.modulesアプローチは、アプリケーションのエントリポイントの外側から呼び出されると機能しなくなります。具体的には、子スレッドがこれを ' main 'モジュールに関係なく判断できないことを確認しました。子スレッドからのインポートを示すために、関数内に明示的にインポートを配置しました。それを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を正確に見つけたいと思います。threading.main_thread()ただし、Python3.4以降ではスレッドライブラリに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 envのpythonインタープリターは次の場所にあります:

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

これは、アナコンダプロジェクトの固定プロジェクト構造を持つコンダプロジェクトでのみ機能します


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おり、関数が呼び出された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

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