Python - obtém o caminho da estrutura raiz do projeto


127

Eu tenho um projeto Python com um arquivo de configuração na raiz do projeto. O arquivo de configuração precisa ser acessado em alguns arquivos diferentes em todo o projeto.

Portanto, parece algo como: <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(quando b, acesso a.py o arquivo de configuração).

Qual é a maneira melhor / mais fácil de obter o caminho para a raiz do projeto e o arquivo de configuração sem depender de qual arquivo dentro do projeto estou? ou seja, sem usar ../../? É normal presumir que sabemos o nome da raiz do projeto.


não <ROOT>/__init__.pyexiste?
mgilson

Ou o seu arquivo de configuração é um módulo python e você pode acessá-lo facilmente apenas com uma instrução import, ou não é um módulo python e você deve colocá-lo em um local conhecido. Por exemplo $ HOME / .my_project / my_project.conf.
John Smith Opcional

@JohnSmithOptional - é um arquivo JSON. Eu preciso conseguir acessá-lo usando o caminho. Sim. Todas as pastas o incluem.
Shookie

_ É normal presumir que sabemos o nome da raiz do projeto._ Isso significa que você conhece o caminho para o projeto? Não é apenas os.path.join (known_root_name, "configuration.conf") então?
tdelaney

Se for uma configuração de usuário, geralmente usaria algo assim os.path.expanduser('~/.myproject/myproject.conf'). Funciona em Unix e Windows.
John Smith Opcional

Respostas:


157

Você pode fazer isso como o Django faz: definir uma variável para a raiz do projeto a partir de um arquivo que está no nível superior do projeto. Por exemplo, se esta é a aparência da estrutura do seu projeto:

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

Em definitions.pyvocê pode definir (isso requer import os):

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

Assim, com a raiz do projeto conhecida, você pode criar uma variável que aponta para a localização da configuração (isso pode ser definido em qualquer lugar, mas um lugar lógico seria colocá-lo em um local onde as constantes são definidas - por exemplo definitions.py):

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

Em seguida, você pode facilmente acessar a constante (em qualquer um dos outros ficheiros) com a declaração de importação (por exemplo, utils.py): from definitions import CONFIG_PATH.


1
Para incluir o arquivo settings.py dessa forma, será necessário adicionar um __init__.pyarquivo ao diretório raiz do projeto também? Isso deveria estar correto? Acabei de começar com python e não tenho certeza sobre as melhores práticas. Obrigado.
akskap

3
@akskap: Não, __init__.pynão será necessário, já que esse arquivo só é necessário ao definir pacotes: Os __init__.pyarquivos são necessários para fazer o Python tratar os diretórios como contendo pacotes; isso é feito para evitar que diretórios com um nome comum, como string, ocultem acidentalmente módulos válidos que ocorrem posteriormente no caminho de pesquisa do módulo. No caso mais simples, __init__.pypode ser apenas um arquivo vazio, mas também pode executar o código de inicialização do pacote ou definir a __all__variável, descrita posteriormente. Consulte: docs.python.org/3/tutorial/modules.html#packages
jrd1

Estou curioso, quanto ao estilo, se é aceitável ou desaprovado adicionar essas definições ao do __init.py__pacote raiz. Isso economizaria a criação de outro arquivo, bem como permitiria uma sintaxe mais agradável de from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6

@ Johndt6: a convenção é manter o __init__.pyvazio, mas isso não é estritamente verdadeiro (afinal, é uma convenção). Veja mais: stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor: não - no exemplo que você citou, os.path.abspathestá chamando uma string '__file__',. Lembre-se de que, __file__na verdade, é um atributo de importação definido para módulos Python. Neste caso, __file__retornará o nome do caminho a partir do qual o módulo foi carregado. Leia mais aqui (consulte a seção de módulos): docs.python.org/3/reference/datamodel.html
jrd1

61

Outras respostas conselhos para usar um arquivo no nível superior do projeto. Isso não é necessário se você usar pathlib.Pathe parent(Python 3.4 e superior). Considere a seguinte estrutura de diretório onde todos os arquivos exceto README.mde utils.pyforam omitidos.

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

Em utils.pynós definimos a seguinte função.

from pathlib import Path

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

Em qualquer módulo do projeto, agora podemos obter a raiz do projeto da seguinte maneira.

from src.utils import get_project_root

root = get_project_root()

Benefícios : Qualquer módulo que chama get_project_rootpode ser movido sem alterar o comportamento do programa. Somente quando o módulo utils.pyé movido, temos que atualizarget_project_root e importar (ferramentas de refatoração podem ser usadas para automatizar isso).


2
Qualquer módulo que está na raiz. Chamar src.utils de fora da raiz não deve funcionar. Estou errado?
aerijman

nome ' arquivo ' não está definido, por quê?
Luk Aron

26

Todas as soluções anteriores parecem ser excessivamente complicadas para o que eu acho que você precisa, e muitas vezes não funcionaram para mim. O seguinte comando de uma linha faz o que você deseja:

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

3
Coloque isso em config.py, na raiz do diretório, .. bamn! Você conseguiu um singleton.
swdev

2
Este método pressupõe que você execute o aplicativo de dentro do caminho que ele existe. Muitos "usuários" têm um ícone em que clicam em uma área de trabalho ou podem executar o aplicativo em outro diretório.
DevPlayer de

23

Para obter o caminho do módulo "raiz", você pode usar:

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

Mas o mais interessante é que, se você tiver um "objeto" de configuração em seu módulo superior, poderá -lê-lo- desta forma:

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

1
Aqui osnão está disponível por padrão. Precisa importar os. Portanto, adicionar a linha import ostornaria a resposta mais completa.
Md. Abu Nafee Ibna Zahid

5
Isso fornece o diretório que contém o script que foi executado. Por exemplo, ao executar, python3 -m topmodule.submodule.scriptele dará em /path/to/topmodule/submodulevez de /path/to/topmodule.
danijar,

14

Uma maneira padrão de conseguir isso seria usar o pkg_resourcesmódulo que faz parte dosetuptools pacote. setuptoolsé usado para criar um pacote python instalável.

Você pode usar pkg_resourcespara retornar o conteúdo do arquivo desejado como uma string e você pode usarpkg_resources para obter o caminho real do arquivo desejado em seu sistema.

Digamos que você tenha um pacote chamado stackoverflow.

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

3 directories, 7 files

Agora, digamos que você deseja acessar o arquivo Rush de um módulo app.run. Use pkg_resources.resouces_filenamepara obter o caminho para o Rush e pkg_resources.resource_stringpara obter o conteúdo do Rush; assim:

import pkg_resources

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

A saída:

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

Isso funciona para todos os pacotes em seu caminho python. Então, se você quiser saber onde lxml.etreeexiste em seu sistema:

import pkg_resources

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

resultado:

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

O ponto é que você pode usar este método padrão para acessar arquivos que estão instalados em seu sistema (por exemplo, pip install xxx ou yum -y install python-xxx) e arquivos que estão dentro do módulo no qual você está trabalhando atualmente.


1
Eu gosto da sua escolha de banda!
dylan_fan

3

Experimentar:

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

1
Isso é exatamente o que eu precisava. Uma solução simples funciona para mim porque minha estrutura era root-> config-> conf.py. Eu queria definir a raiz do projeto em conf.py e root estava exatamente dois níveis acima desse arquivo.
Daniyal Arshad


2

Eu também lutei com esse problema até chegar a essa solução. Esta é a solução mais limpa na minha opinião.

Em seu setup.py adicione "pacotes"

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

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

Usar um ambiente virtual e instalar o pacote com python3 setup.py installele não apontava mais para a pasta do código-fonte, mas para o ovo dentro ~./virtualenv/..../app.egg. Então, eu tive que incluir o arquivo de configuração na instalação do pacote.
loxosceles

2

Apenas um exemplo: desejo executar runio.py de dentro de helper1.py

Exemplo de árvore de projeto:

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

Obtenha a raiz do projeto:

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

Construir caminho para o script:

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

1

Isso funcionou para mim usando um projeto PyCharm padrão com meu ambiente virtual (venv) sob o diretório raiz do projeto.

O código abaixo não é o mais bonito, mas sempre obtém a raiz do projeto. Ele retorna o caminho completo do diretório para venv da VIRTUAL_ENVvariável de ambiente, por exemplo/Users/NAME/documents/PROJECT/venv

Em seguida, ele divide o caminho por último /, fornecendo uma matriz com dois elementos. O primeiro elemento será o caminho do projeto, por exemplo/Users/NAME/documents/PROJECT

import os

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

3
Isso não funcionará com configurações como anaconda ou pipenv, uma vez que o ambiente virtual não está contido no projeto nesses casos.
Gripp

1

Recentemente, tenho tentado fazer algo semelhante e descobri que essas respostas são inadequadas para meus casos de uso (uma biblioteca distribuída que precisa detectar a raiz do projeto). Principalmente, tenho lutado contra diferentes ambientes e plataformas, e ainda não encontrei algo perfeitamente universal.

Código local para projeto

Eu vi este exemplo mencionado e usado em alguns lugares, Django, etc.

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

Por mais simples que seja, só funciona quando o arquivo em que o snippet está, na verdade, faz parte do projeto. Não recuperamos o diretório do projeto, mas sim o diretório do snippet

Da mesma forma, a abordagem sys.modules falha quando chamada de fora do ponto de entrada do aplicativo, especificamente, observei que um thread filho não pode determinar isso sem relação com o módulo ' principal '. Coloquei explicitamente a importação dentro de uma função para demonstrar uma importação de um thread filho, movê-la para o nível superior de app.py resolveria isso.

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

A execução deste programa produz um erro de atributo:

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

... portanto, uma solução baseada em threading

Independente de localização

Usando a mesma estrutura de aplicativo de antes, mas 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))

Resolvendo isso: Primeiro, queremos encontrar com precisão o ID do thread principal. No Python3.4 +, a biblioteca de threading tem threading.main_thread(), entretanto, todo mundo não usa 3.4+, então pesquisamos todos os threads procurando pelo thread principal, exceto seu ID. Se o tópico principal já foi encerrado, ele não será listado no threading.enumerate(). Levantamos um RuntimeError()neste caso até encontrar uma solução melhor.

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

Em seguida, encontramos o primeiro frame de pilha do thread principal. Usando a função específica cPython sys._current_frames() , obtemos um dicionário de cada frame de pilha atual do thread. Em seguida, utilizando inspect.getouterframes(), podemos recuperar a pilha inteira para o thread principal e o primeiro quadro. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Finalmente, as diferenças entre as implementações do Windows e do Linux inspect.getouterframes()precisam ser tratadas. Use o nome do arquivo limpo os.path.abspath()e os.path.dirname()limpe as coisas.

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

Até agora, testei isso no Python 2.7 e 3.6 no Windows, bem como no Python3.4 no WSL


0

Se você estiver trabalhando com o projeto anaconda, você pode consultar o PROJECT_ROOT da variável de ambiente -> os.getenv ('PROJECT_ROOT'). Isso funciona apenas se o script for executado através da execução do projeto anaconda.

Se você não quiser que seu script seja executado pelo anaconda-project, você pode consultar o caminho absoluto do binário executável do interpretador Python que você está usando e extrair a string do caminho até o diretório envs exclusiv. Por exemplo: O interpretador python do meu env conda está localizado em:

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

Isso funciona apenas com o projeto conda com estrutura de projeto fixa de um projeto anaconda


0

Usei o método ../ para buscar o caminho do projeto atual.

Exemplo: Projeto1 - D: \ projetos

src

ConfigurationFiles

Configuration.cfg

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


0

No momento em que este artigo foi escrito, nenhuma das outras soluções era muito independente. Eles dependem de uma variável de ambiente ou da posição do módulo na estrutura do pacote. A melhor resposta com a solução 'Django' é vítima da última, exigindo uma importação relativa. Também tem a desvantagem de ter que modificar um módulo no nível superior.

Esta deve ser a abordagem correta para encontrar o caminho do diretório do pacote de nível superior:

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

Ele funciona pegando o primeiro componente na string pontilhada contida __name__e usando-o como uma chave na sys.modulesqual retorna o objeto de módulo do pacote de nível superior. Seu __file__atributo contém o caminho que queremos depois de cortar/__init__.py usando os.path.dirname().

Esta solução é independente. Ele funciona em qualquer lugar em qualquer módulo do pacote, incluindo no __init__.pyarquivo de nível superior .


Você poderia adicionar uma breve descrição sobre sua solução e como eles podem usá-la como solução?
LuRsT

0

Tive que implementar uma solução customizada porque não é tão simples quanto você imagina. Minha solução é baseada na inspeção de rastreamento de pilha ( inspect.stack()) + sys.pathe está funcionando bem, não importa a localização do módulo python em que a função é chamada nem o interpretador (tentei executá-lo no PyCharm, em um shell de poesia e outros ... ) Esta é a implementação completa com comentários:

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

Há muitas respostas aqui, mas não consegui encontrar algo simples que abranja todos os casos, então permita-me sugerir minha solução também:

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.