Python - Ottieni il percorso della struttura del progetto radice


127

Ho un progetto Python con un file di configurazione nella radice del progetto. È necessario accedere al file di configurazione in alcuni file diversi in tutto il progetto.

Quindi sembra qualcosa di simile: <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(quando b, accesso a.py il file di configurazione).

Qual è il modo migliore / più semplice per ottenere il percorso della radice del progetto e del file di configurazione senza dipendere dal file all'interno del progetto in cui mi trovo? cioè senza usare ../../? Va bene presumere di conoscere il nome della radice del progetto.


non <ROOT>/__init__.pyesiste?
mgilson

O il tuo file di configurazione è un modulo python e puoi accedervi facilmente solo con un'istruzione import, o non è un modulo python e dovresti metterlo in una posizione ben nota. Ad esempio $ HOME / .my_project / my_project.conf.
John Smith Opzionale

@JohnSmithOptional - È un file JSON. Devo essere in grado di accedervi utilizzando il percorso. Sì. Tutte le cartelle lo includono.
Shookie

_ Va bene presumere di conoscere il nome della radice del progetto ._ Significa che conosci il percorso del progetto? Non è solo os.path.join (known_root_name, "configuration.conf") allora?
tdelaney

Se si tratta di una configurazione utente, generalmente userò qualcosa di simile os.path.expanduser('~/.myproject/myproject.conf'). Funziona su Unix e Windows.
John Smith Opzionale

Risposte:


157

Puoi farlo come fa Django: definire una variabile nella radice del progetto da un file che si trova nel livello più alto del progetto. Ad esempio, se questo è l'aspetto della struttura del tuo progetto:

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

In definitions.pypuoi definire (questo richiede import os):

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

Pertanto, con la radice del progetto nota, è possibile creare una variabile che punta alla posizione della configurazione (questa può essere definita ovunque, ma una posizione logica sarebbe metterla in una posizione in cui sono definite le costanti, ad esempio definitions.py):

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

Quindi, è possibile accedere facilmente la costante (in uno qualsiasi degli altri file) con l'istruzione import (ad esempio in utils.py): from definitions import CONFIG_PATH.


1
Per includere il file Definition.py in questo modo, sarà necessario aggiungere un __init__.pyfile anche alla directory principale del progetto? Dovrebbe essere corretto? Ho appena iniziato con Python e non sono sicuro delle migliori pratiche. Grazie.
akskap

3
@akskap: No, __init__.pynon sarà richiesto, poiché quel file è richiesto solo quando si definiscono i pacchetti: I __init__.pyfile sono richiesti per fare in modo che Python tratti le directory come contenenti pacchetti; ciò viene fatto per evitare che directory con un nome comune, come stringa, nascondano involontariamente moduli validi che si verificano successivamente nel percorso di ricerca del modulo. Nel caso più semplice, __init__.pypuò essere solo un file vuoto, ma può anche eseguire il codice di inizializzazione del pacchetto o impostare la __all__variabile, descritta più avanti. Vedi: docs.python.org/3/tutorial/modules.html#packages
jrd1

Sono curioso, dal punto di vista dello stile, se sia accettabile o disapprovato aggiungere queste definizioni al __init.py__pacchetto di root. Risparmierebbe la creazione di un altro file, oltre a consentire la sintassi più piacevole di from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6

@ Johndt6: la convenzione è di rimanere __init__.pyvuoti, ma non è del tutto vero (dopotutto è una convenzione). Vedere questo per più: stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor: no - nell'esempio hai citato, os.path.abspathsta chiamando una stringa, '__file__'. Ricorda che in __file__realtà è un attributo di importazione definito per i moduli Python. In questo caso, __file__restituirà il percorso da cui è stato caricato il modulo. Leggi di più qui (vedi la sezione moduli): docs.python.org/3/reference/datamodel.html
jrd1

62

Altre risposte consigli per utilizzare un file nel livello superiore del progetto. Questo non è necessario se usi pathlib.Pathe parent(Python 3.4 e versioni successive). Considera la seguente struttura di directory in cui tutti i file tranne README.mde utils.pysono stati omessi.

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

In utils.pydefiniamo la seguente funzione.

from pathlib import Path

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

In qualsiasi modulo del progetto ora possiamo ottenere la radice del progetto come segue.

from src.utils import get_project_root

root = get_project_root()

Vantaggi : qualsiasi modulo che chiama get_project_rootpuò essere spostato senza modificare il comportamento del programma. Solo quando il modulo utils.pyviene spostato dobbiamo aggiornare get_project_roote importare (gli strumenti di refactoring possono essere utilizzati per automatizzare questo).


2
Qualsiasi modulo che si trova nella radice. Chiamare src.utils dall'esterno della radice non dovrebbe funzionare. Ho sbagliato?
aerijman

il nome ' file ' non è definito, perché?
Luk Aron

26

Tutte le soluzioni precedenti sembrano essere eccessivamente complicate per quello che penso tu abbia bisogno, e spesso non hanno funzionato per me. Il seguente comando di una riga fa quello che vuoi:

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

3
Mettilo in config.py, alla radice della directory, .. bamn! Ti sei fatto un singleton.
swdev

2
Questo metodo presume che tu esegua l'applicazione dall'interno del percorso che esiste. Molti "utenti" hanno un'icona su cui fanno clic da un desktop o possono eseguire l'app da un'altra directory completamente.
DevPlayer

23

Per ottenere il percorso del modulo "root", puoi utilizzare:

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

Ma la cosa più interessante è che se hai un "oggetto" di configurazione nel tuo modulo più in alto potresti -leggerlo- in questo modo:

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

1
Qui osnon è disponibile per impostazione predefinita. È necessario importare os. Quindi l'aggiunta della riga import osrenderebbe la risposta più completa.
Md. Abu Nafee Ibna Zahid

5
Questo fornisce la directory che contiene lo script che è stato eseguito. Ad esempio, durante l'esecuzione python3 -m topmodule.submodule.scriptdarà /path/to/topmodule/submoduleinvece di /path/to/topmodule.
danijar

14

Un modo standard per ottenere ciò sarebbe utilizzare il pkg_resourcesmodulo che fa parte del setuptoolspacchetto. setuptoolsviene utilizzato per creare un pacchetto Python installabile.

È possibile utilizzare pkg_resourcesper restituire il contenuto del file desiderato come una stringa e utilizzare pkg_resourcesper ottenere il percorso effettivo del file desiderato sul sistema.

Supponiamo che tu abbia un pacchetto chiamato stackoverflow.

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

3 directories, 7 files

Supponiamo ora che tu voglia accedere al file Rush da un modulo app.run. Utilizzare pkg_resources.resouces_filenameper ottenere il percorso per Rush e pkg_resources.resource_stringper ottenere i contenuti di Rush; nel seguente modo:

import pkg_resources

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

Il risultato:

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

Funziona per tutti i pacchetti nel tuo percorso python. Quindi, se vuoi sapere dove lxml.etreeesiste sul tuo sistema:

import pkg_resources

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

produzione:

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

Il punto è che puoi utilizzare questo metodo standard per accedere ai file installati sul tuo sistema (ad esempio pip install xxx o yum -y install python-xxx) e ai file che si trovano all'interno del modulo su cui stai attualmente lavorando.


1
Mi piace la scelta della tua band!
dylan_fan


3

Provare:

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

1
Questo è esattamente ciò di cui avevo bisogno. Soluzione semplice, funziona per me perché la mia struttura era root-> config-> conf.py Volevo definire la root del progetto in conf.py e root era esattamente due livelli sopra quel file.
Daniyal Arshad

2

Ho lottato anche con questo problema fino a quando non sono arrivato a questa soluzione. Questa è la soluzione più pulita secondo me.

Nel tuo setup.py aggiungi "packages"

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

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

Usare un ambiente virtuale e installare il pacchetto con python3 setup.py installesso non puntava più alla cartella del codice sorgente, ma all'uovo all'interno ~./virtualenv/..../app.egg. Quindi ho dovuto includere il file di configurazione nell'installazione del pacchetto.
loxosceles

2

Solo un esempio: voglio eseguire runio.py da helper1.py

Esempio di albero del progetto:

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

Ottieni la radice del progetto:

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

Crea percorso per script:

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

1

Questo ha funzionato per me utilizzando un progetto PyCharm standard con il mio ambiente virtuale (venv) nella directory principale del progetto.

Il codice seguente non è il più carino, ma ottiene costantemente la radice del progetto. Restituisce il percorso completo della directory a venv dalla VIRTUAL_ENVvariabile d'ambiente es/Users/NAME/documents/PROJECT/venv

Quindi divide il percorso all'ultimo /, dando un array con due elementi. Il primo elemento sarà il percorso del progetto es/Users/NAME/documents/PROJECT

import os

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

3
Questo non funzionerà con configurazioni come anaconda o pipenv, poiché l'ambiente virtuale non è contenuto nel progetto in quei casi.
Gripp

1

Recentemente ho provato a fare qualcosa di simile e ho trovato queste risposte inadeguate per i miei casi d'uso (una libreria distribuita che deve rilevare la radice del progetto). Principalmente ho combattuto diversi ambienti e piattaforme e non ho ancora trovato qualcosa di perfettamente universale.

Codice locale al progetto

Ho visto questo esempio menzionato e utilizzato in alcuni posti, Django, ecc.

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

Per quanto semplice, funziona solo quando il file in cui si trova lo snippet fa effettivamente parte del progetto. Non recuperiamo la directory del progetto, ma la directory dello snippet

Allo stesso modo, l' approccio sys.modules si interrompe quando viene chiamato dall'esterno del punto di ingresso dell'applicazione, in particolare ho osservato che un thread figlio non può determinarlo senza relazione con il modulo " principale ". Ho inserito esplicitamente l'importazione in una funzione per dimostrare un'importazione da un thread figlio, spostandola al livello superiore di app.py lo risolverebbe.

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

L'esecuzione di questo programma produce un errore di attributo:

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

... quindi una soluzione basata sul threading

Posizione indipendente

Utilizzando la stessa struttura dell'applicazione di prima ma modificando 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))

Rompendo questo: per prima cosa vogliamo trovare con precisione l'ID thread del thread principale. In Python3.4 + la libreria di threading ha threading.main_thread()tuttavia, tutti non usano 3.4+ quindi cerchiamo in tutti i thread cercando il thread principale salvando il suo ID. Se il thread principale è già terminato, non verrà elencato nel file threading.enumerate(). Solleviamo una RuntimeError()in questo caso finché non trovo una soluzione migliore.

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

Successivamente troviamo il primo stack frame del thread principale. Usando la funzione specifica di cPython sys._current_frames() otteniamo un dizionario dello stack frame corrente di ogni thread. Quindi utilizzando inspect.getouterframes()possiamo recuperare l'intero stack per il thread principale e il primissimo frame. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Infine, le differenze tra le implementazioni Windows e Linux inspect.getouterframes()devono essere gestite. Utilizzando il nome del file ripulito os.path.abspath()e os.path.dirname()ripulire le cose.

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

Finora l'ho testato su Python2.7 e 3.6 su Windows e su Python3.4 su WSL


0

Se stai lavorando con anaconda-project, puoi interrogare PROJECT_ROOT dalla variabile d'ambiente -> os.getenv ('PROJECT_ROOT'). Funziona solo se lo script viene eseguito tramite anaconda-project run.

Se non vuoi che il tuo script venga eseguito da anaconda-project, puoi interrogare il percorso assoluto del binario eseguibile dell'interprete Python che stai usando ed estrarre la stringa del percorso fino alla directory envs exclusiv. Ad esempio: l'interprete python del mio conda env si trova in:

/ Home / user / project_root / ENV / 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]

Funziona solo con conda-project con struttura di progetto fissa di un anaconda-project


0

Ho usato il metodo ../ per recuperare il percorso del progetto corrente.

Esempio: Project1 - D: \ projects

src

ConfigurationFiles

Configuration.cfg

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


0

Al momento della scrittura, nessuna delle altre soluzioni è molto autonoma. Dipendono da una variabile di ambiente o dalla posizione del modulo nella struttura del pacchetto. La prima risposta con la soluzione "Django" cade vittima di quest'ultima richiedendo un'importazione relativa. Ha anche lo svantaggio di dover modificare un modulo al livello più alto.

Questo dovrebbe essere l'approccio corretto per trovare il percorso della directory del pacchetto di primo livello:

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

Funziona prendendo il primo componente nella stringa punteggiata contenuta in __name__e usandolo come chiave in sys.modulescui restituisce l'oggetto modulo del pacchetto di primo livello. Il suo __file__attributo contiene il percorso che vogliamo dopo aver tagliato /__init__.pyusando os.path.dirname().

Questa soluzione è autonoma. Funziona ovunque in qualsiasi modulo del pacchetto, incluso nel __init__.pyfile di primo livello .


Potresti aggiungere una breve descrizione della tua soluzione e di come possono usarla come soluzione?
LuRsT

0

Ho dovuto implementare una soluzione personalizzata perché non è così semplice come potresti pensare. La mia soluzione si basa sull'ispezione della traccia dello stack ( inspect.stack()) + sys.pathe funziona bene indipendentemente dalla posizione del modulo python in cui viene richiamata la funzione né dall'interprete (ho provato eseguendolo in PyCharm, in una shell di poesia e altro ... ). Questa è l'implementazione completa con commenti:

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

Ci sono molte risposte qui ma non sono riuscito a trovare qualcosa di semplice che copra tutti i casi, quindi permettimi di suggerire anche la mia soluzione:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.