Python - Pfad der Stammprojektstruktur abrufen

126

Ich habe ein Python-Projekt mit einer Konfigurationsdatei im Projektstamm. Auf die Konfigurationsdatei muss während des gesamten Projekts in einigen verschiedenen Dateien zugegriffen werden.

Es sieht also ungefähr so aus : <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(wenn b, a.py auf die Konfigurationsdatei zugreifen).

Was ist der beste / einfachste Weg, um den Pfad zum Projektstamm und zur Konfigurationsdatei zu erhalten, ohne davon abhängig zu sein, in welcher Datei sich das Projekt befindet, in dem ich mich befinde? dh ohne zu benutzen ../../? Es ist in Ordnung anzunehmen, dass wir den Namen des Projektstamms kennen.

Shookie
quelle
nicht <ROOT>/__init__.pyexistieren?
mgilson
Entweder ist Ihre Konfigurationsdatei ein Python-Modul, und Sie können einfach mit einer Importanweisung darauf zugreifen. Entweder handelt es sich nicht um ein Python-Modul, und Sie sollten es an einem bekannten Speicherort ablegen. Zum Beispiel $ HOME / .my_project / my_project.conf.
John Smith Optional
@ JohnSmithOptional - Es ist eine JSON-Datei. Ich muss über den Pfad darauf zugreifen können. Ja. Alle Ordner enthalten es.
Shookie
Es ist in Ordnung anzunehmen, dass wir den Namen des Projektstamms kennen. Bedeutet das, dass Sie den Pfad zum Projekt kennen? Ist es dann nicht nur os.path.join (bekannter_Wurzelname, "configuration.conf")?
tdelaney
Wenn es sich um eine Benutzerkonfiguration handelt, würde ich im Allgemeinen so etwas wie verwenden os.path.expanduser('~/.myproject/myproject.conf'). Es funktioniert unter Unix und Windows.
John Smith Optional

Antworten:

157

Sie können dies so tun, wie Django es tut: Definieren Sie eine Variable für den Projektstamm aus einer Datei, die sich in der obersten Ebene des Projekts befindet. Wenn Ihre Projektstruktur beispielsweise so aussieht:

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

In können definitions.pySie definieren (dies erfordert import os):

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

Mit dem bekannten Projektstamm können Sie also eine Variable erstellen, die auf den Speicherort der Konfiguration verweist (dies kann überall definiert werden, aber ein logischer Ort wäre, sie an einem Speicherort zu platzieren, an dem Konstanten definiert sind - z. B. definitions.py):

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

Anschließend können Sie mit der import-Anweisung (z . B. in utils.py) einfach auf die Konstante (in einer der anderen Dateien) zugreifen : from definitions import CONFIG_PATH.

jrd1
quelle
1
Müssen Sie auch eine __init__.pyDatei zum Stammprojektverzeichnis hinzufügen, um die Datei definition.py so einzuschließen ? Sollte das richtig sein? Ich habe gerade mit Python angefangen und bin mir nicht sicher, welche Best Practices es gibt. Vielen Dank.
Akskap
3
@akskap: Nein, ein __init__.pywird nicht benötigt, da diese Datei nur beim Definieren von Paketen erforderlich ist: Die __init__.pyDateien sind erforderlich, damit Python die Verzeichnisse als Pakete enthaltend behandelt. Auf diese Weise wird verhindert, dass Verzeichnisse mit einem allgemeinen Namen, z. B. eine Zeichenfolge, unbeabsichtigt gültige Module verbergen, die später im Modul-Suchpfad auftreten. Im einfachsten Fall __init__.pykann es sich nur um eine leere Datei handeln, es kann jedoch auch der Initialisierungscode für das Paket ausgeführt oder die __all__später beschriebene Variable festgelegt werden. Siehe: docs.python.org/3/tutorial/modules.html#packages
jrd1
Ich bin stilistisch neugierig, ob es akzeptabel oder verpönt ist, diese Definitionen dem Stammpaket hinzuzufügen __init.py__. Es würde das Erstellen einer weiteren Datei speichern und die schönere Syntax von zulassen from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6
@ Johndt6: Die Konvention soll __init__.pyleer bleiben , aber das ist nicht unbedingt wahr (es ist schließlich eine Konvention). Weitere Informationen finden
Sie hier
1
@JavNoor: nein - os.path.abspathruft in dem von Ihnen zitierten Beispiel einen String auf , '__file__'. Denken Sie daran, dass dies __file__tatsächlich ein Importattribut ist, das für Python-Module definiert ist. In diesem Fall __file__wird der Pfadname zurückgegeben, von dem das Modul geladen wird. Lesen Sie hier mehr (siehe Abschnitt Module): docs.python.org/3/reference/datamodel.html
jrd1
61

Andere Antworten empfehlen, eine Datei in der obersten Ebene des Projekts zu verwenden. Dies ist nicht erforderlich, wenn Sie pathlib.Pathund parent(Python 3.4 und höher) verwenden. Betrachten Sie die folgende Verzeichnisstruktur, in der alle Dateien außer README.mdund utils.pyweggelassen wurden.

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

In utils.pydefinieren wir die folgende Funktion.

from pathlib import Path

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

In jedem Modul im Projekt können wir jetzt den Projektstamm wie folgt abrufen.

from src.utils import get_project_root

root = get_project_root()

Vorteile : Jedes Modul, das aufruft, get_project_rootkann verschoben werden, ohne das Programmverhalten zu ändern. Erst wenn das Modul utils.pyverschoben wird, müssen wir es aktualisieren get_project_rootund importieren (Refactoring-Tools können verwendet werden, um dies zu automatisieren).

RikH
quelle
2
Jedes Modul, das sich im Stammverzeichnis befindet. Das Aufrufen von src.utils von außerhalb des Stammverzeichnisses sollte nicht funktionieren. Liege ich falsch?
Aerijman
Name ' Datei ' ist nicht definiert, warum?
Luk Aron
26

Alle vorherigen Lösungen scheinen für das, was ich denke, dass Sie brauchen, zu kompliziert zu sein und haben bei mir oft nicht funktioniert. Der folgende einzeilige Befehl macht, was Sie wollen:

import os
ROOT_DIR = os.path.abspath(os.curdir)
Martim
quelle
3
Fügen Sie das in config.py im Stammverzeichnis des Verzeichnisses ein. .. bamn! Du hast dir einen Singleton besorgt.
Swdev
1
Diese Methode setzt voraus, dass Sie die Anwendung über den vorhandenen Pfad ausführen. Viele "Benutzer" haben ein Symbol, auf das sie von einem Desktop aus klicken oder die App vollständig aus einem anderen Verzeichnis ausführen können.
DevPlayer
23

Um den Pfad des "root" -Moduls abzurufen, können Sie Folgendes verwenden:

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

Interessanter ist jedoch, dass Sie, wenn Sie ein Konfigurations- "Objekt" in Ihrem obersten Modul haben, wie folgt daraus lesen können:

app = sys.modules['__main__']
stuff = app.config.somefunc()
DevPlayer
quelle
1
Hier osist standardmäßig nicht verfügbar. Müssen importieren os. Das Hinzufügen der Zeile import oswürde die Antwort also vollständiger machen.
Md. Abu Nafee Ibna Zahid
4
Dies gibt das Verzeichnis an, das das ausgeführte Skript enthält. Zum Beispiel wird es beim Ausführen statt python3 -m topmodule.submodule.scriptgeben . /path/to/topmodule/submodule/path/to/topmodule
Danijar
14

Ein Standardweg, um dies zu erreichen, wäre die Verwendung des pkg_resourcesModuls, das Teil des setuptoolsPakets ist. setuptoolswird verwendet, um ein installierbares Python-Paket zu erstellen.

Sie können pkg_resourcesden Inhalt Ihrer gewünschten Datei als Zeichenfolge zurückgeben und pkg_resourcesden tatsächlichen Pfad der gewünschten Datei auf Ihrem System abrufen.

Angenommen, Sie haben ein Paket namens stackoverflow.

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

3 directories, 7 files

Angenommen, Sie möchten von einem Modul aus auf die Datei Rush zugreifen app.run. Verwenden Sie pkg_resources.resouces_filenamediese Option , um den Pfad zu Rush und pkg_resources.resource_stringden Inhalt von Rush abzurufen. also:

import pkg_resources

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

Die Ausgabe:

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

Dies funktioniert für alle Pakete in Ihrem Python-Pfad. Wenn Sie also wissen möchten, wo lxml.etreeauf Ihrem System vorhanden ist:

import pkg_resources

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

Ausgabe:

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

Der Punkt ist, dass Sie diese Standardmethode verwenden können, um auf Dateien zuzugreifen, die auf Ihrem System installiert sind (z. B. pip install xxx oder yum -y install python-xxx) und auf Dateien, die sich in dem Modul befinden, an dem Sie gerade arbeiten.

Spitzmaus
quelle
1
Ich mag deine Bandwahl!
Dylan_fan
3

Versuchen:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Harry
quelle
1
Genau das habe ich gebraucht. Einfache Lösung, funktioniert für mich, weil meine Struktur root-> config-> conf.py war. Ich wollte das Projekt root in conf.py definieren und root war genau zwei Ebenen höher als diese Datei.
Daniyal Arshad
3

Unter Code Gibt den Pfad bis zu Ihrem Projektstamm zurück

import sys
print(sys.path[1])
Arpan Saini
quelle
Netter Tipp! Ich frage mich, warum niemand außer mir Ihre Antwort positiv bewertet hat: P
daveoncode
Danke Daveon, weiß das wirklich zu schätzen !!
Arpan Saini
Leider ist das nicht einfach: P ... sehen Sie sich meine vollständige Lösung an: stackoverflow.com/a/62510836/267719
daveoncode
2

Ich hatte auch mit diesem Problem zu kämpfen, bis ich zu dieser Lösung kam. Dies ist meiner Meinung nach die sauberste Lösung.

Fügen Sie in Ihrer setup.py "Pakete" hinzu

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

In Ihrer 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')
Kerl
quelle
Die Verwendung einer virtuellen Umgebung und die Installation des Pakets python3 setup.py installdamit zeigte nicht mehr auf den Quellcodeordner, sondern auf das Ei im Inneren ~./virtualenv/..../app.egg. Also musste ich die Konfigurationsdatei in die Paketinstallation aufnehmen.
loxosceles
2

Nur ein Beispiel: Ich möchte runio.py in helper1.py ausführen

Beispiel für einen Projektbaum:

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

Projektstamm abrufen:

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

Pfad zum Skript erstellen:

runme = os.path.join(rootdir, "tools_dir", "runio.py")
execfile(runme)
Alex Granovsky
quelle
1

Dies funktionierte bei Verwendung eines Standard-PyCharm-Projekts mit meiner virtuellen Umgebung (venv) im Projektstammverzeichnis.

Der folgende Code ist nicht der schönste, erhält aber konsistent den Projektstamm. Es gibt den vollständigen Verzeichnispfad von der VIRTUAL_ENVUmgebungsvariablen, z/Users/NAME/documents/PROJECT/venv

Anschließend wird der Pfad zuletzt aufgeteilt /, wodurch ein Array mit zwei Elementen erstellt wird. Das erste Element ist der Projektpfad, z/Users/NAME/documents/PROJECT

import os

print(os.path.split(os.environ['VIRTUAL_ENV'])[0])
Gaz_Edge
quelle
3
Dies funktioniert nicht mit Setups wie anaconda oder pipenv, da die virtuelle Umgebung in diesen Fällen nicht im Projekt enthalten ist.
Gripp
1

Ich habe kürzlich versucht, etwas Ähnliches zu tun, und ich habe festgestellt, dass diese Antworten für meine Anwendungsfälle unzureichend sind (eine verteilte Bibliothek, die den Projektstamm erkennen muss). Hauptsächlich habe ich mit verschiedenen Umgebungen und Plattformen gekämpft und immer noch nichts vollkommen Universelles gefunden.

Lokaler Code für das Projekt

Ich habe dieses Beispiel erwähnt und an einigen Stellen verwendet, Django usw.

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

So einfach das ist, es funktioniert nur, wenn die Datei, in der sich das Snippet befindet, tatsächlich Teil des Projekts ist. Wir rufen nicht das Projektverzeichnis ab, sondern das Verzeichnis des Snippets

In ähnlicher Weise bricht der sys.modules- Ansatz zusammen, wenn er von außerhalb des Einstiegspunkts der Anwendung aufgerufen wird. Insbesondere habe ich festgestellt, dass ein untergeordneter Thread dies nicht ohne Bezug zum Hauptmodul feststellen kann . Ich habe den Import explizit in eine Funktion eingefügt, um einen Import aus einem untergeordneten Thread zu demonstrieren. Wenn Sie ihn auf die oberste Ebene von app.py verschieben, wird dies behoben.

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

Das Ausführen dieses Programms führt zu einem Attributfehler:

>>> 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__'

... daher eine Threading-basierte Lösung

Standortunabhängig

Verwenden Sie dieselbe Anwendungsstruktur wie zuvor, ändern Sie jedoch 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))

Aufschlüsselung: Zuerst wollen wir die Thread-ID des Haupt-Threads genau finden. In Python3.4 + hat die Threading-Bibliothek threading.main_thread()jedoch nicht jeder 3.4+ verwendet, sodass wir alle Threads nach dem Haupt-Thread durchsuchen und dessen ID speichern. Wenn der Hauptthread bereits beendet wurde, wird er nicht in der Liste aufgeführt threading.enumerate(). Wir erheben RuntimeError()in diesem Fall eine, bis ich eine bessere Lösung finde.

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

Als nächstes finden wir den allerersten Stapelrahmen des Hauptthreads. Mit der cPython-spezifischen Funktion erhalten sys._current_frames() wir ein Wörterbuch des aktuellen Stapelrahmens jedes Threads. Mit inspect.getouterframes()können wir dann den gesamten Stapel für den Haupt-Thread und den allerersten Frame abrufen. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Schließlich müssen die Unterschiede zwischen Windows- und Linux-Implementierungen von inspect.getouterframes()behandelt werden. Verwenden Sie den bereinigten Dateinamen os.path.abspath()und os.path.dirname()bereinigen Sie die Dinge.

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

Bisher habe ich dies unter Python2.7 und 3.6 unter Windows sowie unter Python3.4 unter WSL getestet

Joseph Burnitz
quelle
0

Wenn Sie mit einem Anaconda-Projekt arbeiten, können Sie PROJECT_ROOT über die Umgebungsvariable -> os.getenv ('PROJECT_ROOT') abfragen. Dies funktioniert nur, wenn das Skript über einen Anaconda-Projektlauf ausgeführt wird.

Wenn Sie nicht möchten, dass Ihr Skript von anaconda-project ausgeführt wird, können Sie den absoluten Pfad der ausführbaren Binärdatei des von Ihnen verwendeten Python-Interpreters abfragen und die Pfadzeichenfolge exklusiv in das Verzeichnis envs extrahieren. Zum Beispiel: Der Python-Interpreter meiner conda env befindet sich unter:

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

Dies funktioniert nur mit Conda-Projekt mit fester Projektstruktur eines Anaconda-Projekts

Domsch
quelle
0

Ich habe die Methode ../ verwendet, um den aktuellen Projektpfad abzurufen.

Beispiel: Projekt1 - D: \ Projekte

src

Konfigurationsdateien

Configuration.cfg

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

Adarsh
quelle
0

Zum Zeitpunkt des Schreibens ist keine der anderen Lösungen sehr eigenständig. Sie hängen entweder von einer Umgebungsvariablen oder von der Position des Moduls in der Paketstruktur ab. Die Top-Antwort mit der 'Django'-Lösung fällt letzterer zum Opfer, indem sie einen relativen Import erfordert. Es hat auch den Nachteil, dass ein Modul auf der obersten Ebene geändert werden muss.

Dies sollte der richtige Ansatz sein, um den Verzeichnispfad des Pakets der obersten Ebene zu finden:

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

Es funktioniert, indem die erste Komponente in der darin enthaltenen gepunkteten Zeichenfolge __name__als Schlüssel verwendet wird, in sys.modulesdem das Modulobjekt des Pakets der obersten Ebene zurückgegeben wird. Sein __file__Attribut enthält den Weg , den wir wollen nach Abschneiden /__init__.pymit os.path.dirname().

Diese Lösung ist in sich geschlossen. Es funktioniert überall in jedem Modul des Pakets, einschließlich in der __init__.pyDatei der obersten Ebene .

Pyprohly
quelle
Können Sie eine kurze Beschreibung Ihrer Lösung hinzufügen und wie sie diese als Lösung verwenden können?
LuRsT
0

Ich musste eine benutzerdefinierte Lösung implementieren, da diese nicht so einfach ist, wie Sie vielleicht denken. Meine Lösung basiert auf Stack Trace Inspection ( inspect.stack()) + sys.pathund funktioniert einwandfrei, unabhängig von der Position des Python-Moduls, in dem die Funktion aufgerufen wird, oder des Interpreters (ich habe versucht, sie in PyCharm, in einer Poetry-Shell und anderen ... ). Dies ist die vollständige Implementierung mit Kommentaren:

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
Daveoncode
quelle
-1

Hier gibt es viele Antworten, aber ich konnte nichts Einfaches finden, das alle Fälle abdeckt. Lassen Sie mich daher auch meine Lösung vorschlagen:

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

alonhzn
quelle