Python - Dapatkan jalur struktur proyek root


127

Saya punya proyek python dengan file konfigurasi di root proyek. File konfigurasi perlu diakses di beberapa file berbeda di seluruh proyek.

Sehingga terlihat seperti: <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(ketika b, akses a.py file konfigurasi).

Apa cara terbaik / termudah untuk mendapatkan jalur ke root proyek dan file konfigurasi tanpa bergantung pada file mana di dalam proyek yang saya ikuti? yaitu tanpa menggunakan ../../? Tidak masalah untuk berasumsi bahwa kita mengetahui nama root proyek.


apakah <ROOT>/__init__.pyada?
mgilson

Entah file konfigurasi Anda adalah modul python, dan Anda dapat dengan mudah mengaksesnya hanya dengan pernyataan import, baik itu bukan modul python dan Anda harus meletakkannya di lokasi yang dikenal. Misalnya $ HOME / .my_project / my_project.conf.
John Smith Opsional

@JohnSmithOptional - Ini adalah file JSON. Saya harus dapat mengaksesnya menggunakan jalur. Iya. Semua folder menyertakannya.
Shookie

_ Tidak masalah untuk berasumsi bahwa kita mengetahui nama root proyek._ Apakah itu berarti Anda mengetahui jalur ke proyek? Bukankah itu hanya os.path.join (known_root_name, "configuration.conf")?
tdelaney

Jika itu adalah konfigurasi pengguna, saya biasanya akan menggunakan sesuatu seperti os.path.expanduser('~/.myproject/myproject.conf'). Ia bekerja pada Unix dan Windows.
John Smith Opsional

Jawaban:


157

Anda dapat melakukan ini bagaimana Django melakukannya: tentukan variabel ke Akar Proyek dari berkas yang ada di tingkat atas proyek. Misalnya, jika seperti ini struktur proyek Anda:

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

Di definitions.pyAnda dapat menentukan (ini membutuhkan import os):

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

Jadi, dengan Project Root diketahui, Anda dapat membuat variabel yang menunjuk ke lokasi konfigurasi (ini dapat didefinisikan di mana saja, tetapi tempat yang logis adalah meletakkannya di lokasi di mana konstanta ditentukan - misalnya definitions.py):

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

Kemudian, Anda dapat dengan mudah mengakses konstan (dalam salah satu file lainnya) dengan pernyataan impor (misalnya dalam utils.py): from definitions import CONFIG_PATH.


1
Untuk menyertakan file definitions.py seperti itu, akankah diperlukan untuk menambahkan __init__.pyfile ke direktori proyek root juga? Haruskah itu benar? Saya baru saja mulai dengan python dan tidak yakin tentang praktik terbaik. Terima kasih.
akskap

3
@akskap: Tidak, seorang __init__.pytidak akan diperlukan, karena file yang hanya diperlukan ketika mendefinisikan paket: The __init__.pyfile yang diperlukan untuk membuat Python memperlakukan direktori sebagai mengandung paket; ini dilakukan untuk mencegah direktori dengan nama umum, seperti string, menyembunyikan modul valid yang terjadi kemudian di jalur pencarian modul secara tidak sengaja. Dalam kasus yang paling sederhana, __init__.pydapat berupa file kosong, tetapi juga dapat menjalankan kode inisialisasi untuk paket atau mengatur __all__variabel, dijelaskan nanti. Lihat: docs.python.org/3/tutorial/modules.html#packages
jrd1

Saya ingin tahu, dari segi gaya, apakah itu dapat diterima atau tidak disukai untuk menambahkan definisi ini ke __init.py__paket root. Ini akan menghemat pembuatan file lain, serta memungkinkan sintaks yang lebih baik from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6

@ Johndt6: konvensi adalah untuk tetap __init__.pykosong, tapi itu tidak sepenuhnya benar (bagaimanapun juga itu adalah konvensi). Lihat ini untuk lebih lanjut: stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor: tidak - dalam contoh yang Anda kutip, os.path.abspathmemanggil string '__file__',. Ingat itu __file__sebenarnya adalah atribut import yang ditentukan untuk modul Python. Dalam hal ini, __file__akan mengembalikan nama jalur tempat modul dimuat. Baca lebih lanjut di sini (lihat bagian modul): docs.python.org/3/reference/datamodel.html
jrd1

62

Jawaban lain saran untuk menggunakan file di tingkat atas proyek. Ini tidak diperlukan jika Anda menggunakan pathlib.Pathdan parent(Python 3.4 dan yang lebih baru). Pertimbangkan struktur direktori berikut di mana semua file kecuali README.mddan utils.pytelah dihilangkan.

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

Dalam utils.pykita mendefinisikan fungsi berikut.

from pathlib import Path

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

Dalam modul mana pun dalam proyek ini, kita sekarang bisa mendapatkan root proyek sebagai berikut.

from src.utils import get_project_root

root = get_project_root()

Manfaat : Setiap modul yang dipanggil get_project_rootdapat dipindahkan tanpa mengubah perilaku program. Hanya ketika modul utils.pydipindahkan kita harus memperbarui get_project_rootdan mengimpor (alat refactoring dapat digunakan untuk mengotomatiskan ini).


2
Modul apa pun yang ada di root. Memanggil src.utils dari luar root seharusnya tidak berfungsi. Apakah aku salah?
aerijman

nama ' file ' tidak ditentukan, mengapa?
Luk Aron

26

Semua solusi sebelumnya tampaknya terlalu rumit untuk apa yang menurut saya Anda butuhkan, dan seringkali tidak berhasil untuk saya. Perintah satu baris berikut melakukan apa yang Anda inginkan:

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

3
Taruh itu di config.py, di akar direktori, .. sial! Anda menjadi seorang lajang.
swdev

2
Metode ini menganggap Anda menjalankan aplikasi dari dalam jalur yang ada. Banyak "pengguna" memiliki ikon yang mereka klik dari desktop atau dapat menjalankan aplikasi dari direktori lain sepenuhnya.
DevPlayer

23

Untuk mendapatkan jalur modul "root", Anda dapat menggunakan:

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

Tetapi yang lebih menarik jika Anda memiliki konfigurasi "objek" di modul paling atas Anda, Anda bisa -membaca- darinya seperti ini:

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

1
Di sini ostidak tersedia secara default. Perlu mengimpor os. Jadi menambahkan garis import osakan membuat jawaban menjadi lebih lengkap.
Md. Abu Nafee Ibna Zahid

5
Ini memberikan direktori yang berisi skrip yang dieksekusi. Misalnya, saat menjalankannya python3 -m topmodule.submodule.scriptakan memberi /path/to/topmodule/submodulealih-alih /path/to/topmodule.
danijar

14

Cara standar untuk mencapai ini adalah dengan menggunakan pkg_resourcesmodul yang merupakan bagian dari setuptoolspaket. setuptoolsdigunakan untuk membuat paket python yang dapat diinstal.

Anda dapat menggunakan pkg_resourcesuntuk mengembalikan konten file yang Anda inginkan sebagai string dan Anda dapat menggunakan pkg_resourcesuntuk mendapatkan jalur sebenarnya dari file yang diinginkan di sistem Anda.

Katakanlah Anda memiliki sebuah paket bernama stackoverflow.

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

3 directories, 7 files

Sekarang katakanlah Anda ingin mengakses file Rush dari modul app.run. Gunakan pkg_resources.resouces_filenameuntuk mendapatkan jalur ke Rush dan pkg_resources.resource_stringuntuk mendapatkan konten Rush; demikian:

import pkg_resources

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

Hasil:

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

Ini berfungsi untuk semua paket di jalur python Anda. Jadi jika Anda ingin tahu di mana lxml.etreeada di sistem Anda:

import pkg_resources

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

keluaran:

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

Intinya adalah Anda dapat menggunakan metode standar ini untuk mengakses file yang diinstal pada sistem Anda (misalnya pip install xxx atau yum -y install python-xxx) dan file yang ada di dalam modul yang sedang Anda kerjakan.


1
Saya suka pilihan band Anda!
dylan_fan


3

Mencoba:

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

1
Inilah yang saya butuhkan. Solusi sederhana, berfungsi untuk saya karena struktur saya adalah root-> config-> conf.py Saya ingin mendefinisikan root proyek di conf.py dan root persis dua level di atas file itu.
Daniyal Arshad

2

Saya berjuang dengan masalah ini juga sampai saya menemukan solusi ini. Ini adalah solusi terbersih menurut saya.

Di setup.py Anda tambahkan "paket"

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

Di python_script.py Anda

import pkg_resources
import os

resource_package = pkg_resources.get_distribution(
    'package_name').location
config_path = os.path.join(resource_package,'configuration.conf')

Menggunakan lingkungan virtual dan menginstal paket python3 setup.py installdengannya tidak lagi mengarah ke folder kode sumber, tetapi ke telur di dalamnya ~./virtualenv/..../app.egg. Jadi saya harus memasukkan file konfigurasi ke dalam paket instalasi.
loxosceles

2

Sebagai contoh: Saya ingin menjalankan runio.py dari dalam helper1.py

Contoh pohon proyek:

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

Dapatkan root proyek:

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

Bangun jalur ke skrip:

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

1

Ini bekerja untuk saya menggunakan proyek PyCharm standar dengan lingkungan virtual saya (venv) di bawah direktori root proyek.

Kode di bawah ini bukan yang tercantik, tetapi secara konsisten mendapatkan root proyek. Ia mengembalikan path direktori lengkap ke venv dari VIRTUAL_ENVvariabel lingkungan misalnya/Users/NAME/documents/PROJECT/venv

Itu kemudian membagi jalur pada akhirnya /, memberikan sebuah array dengan dua elemen. Elemen pertama akan menjadi jalur proyek misalnya/Users/NAME/documents/PROJECT

import os

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

3
Ini tidak akan berfungsi dengan penyiapan seperti anaconda atau pipenv, karena lingkungan virtual tidak terdapat dalam proyek dalam kasus tersebut.
Gripp

1

Saya baru-baru ini mencoba melakukan sesuatu yang serupa dan saya menemukan jawaban ini tidak memadai untuk kasus penggunaan saya (perpustakaan terdistribusi yang perlu mendeteksi root proyek). Terutama saya telah berjuang melawan lingkungan dan platform yang berbeda, dan masih belum menemukan sesuatu yang sangat universal.

Kode lokal untuk proyek

Saya telah melihat contoh ini disebutkan dan digunakan di beberapa tempat, Django, dll.

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

Sesederhana ini, ini hanya berfungsi jika file tempat cuplikan sebenarnya adalah bagian dari proyek. Kami tidak mengambil direktori proyek, melainkan direktori cuplikan

Demikian pula, pendekatan sys.modules rusak ketika dipanggil dari luar titik masuk aplikasi, khususnya saya telah mengamati utas anak tidak dapat menentukan ini tanpa hubungannya kembali ke modul ' utama '. Saya secara eksplisit meletakkan impor di dalam fungsi untuk mendemonstrasikan impor dari utas anak, memindahkannya ke tingkat atas app.py akan memperbaikinya.

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

Menjalankan program ini menghasilkan kesalahan atribut:

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

... maka solusi berbasis threading

Lokasi independen

Menggunakan struktur aplikasi yang sama seperti sebelumnya tetapi memodifikasi 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))

Menguraikannya: Pertama, kami ingin menemukan ID utas utas utama secara akurat. Dalam Python3.4 + perpustakaan threading threading.main_thread()bagaimanapun, semua orang tidak menggunakan 3.4+ jadi kami mencari melalui semua utas mencari utas utama simpan ID-nya. Jika utas utama sudah keluar, utas tidak akan terdaftar di threading.enumerate(). Kami meningkatkan a RuntimeError()dalam kasus ini sampai saya menemukan solusi yang lebih baik.

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

Selanjutnya kita menemukan bingkai tumpukan pertama dari utas utama. Dengan menggunakan fungsi spesifik cPython, sys._current_frames() kami mendapatkan kamus bingkai tumpukan setiap utas saat ini. Kemudian dengan memanfaatkannya, inspect.getouterframes()kita dapat mengambil seluruh tumpukan untuk utas utama dan bingkai pertama. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Akhirnya, perbedaan antara implementasi Windows dan Linux inspect.getouterframes()perlu ditangani. Menggunakan nama file yang dibersihkan, os.path.abspath()dan os.path.dirname()membersihkan semuanya.

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

Sejauh ini saya telah menguji ini di Python2.7 dan 3.6 di Windows serta Python3.4 di WSL


0

Jika Anda bekerja dengan proyek anaconda, Anda dapat meminta PROJECT_ROOT dari variabel lingkungan -> os.getenv ('PROJECT_ROOT'). Ini hanya berfungsi jika skrip dijalankan melalui anaconda-project run.

Jika Anda tidak ingin skrip Anda dijalankan oleh proyek-anaconda, Anda dapat menanyakan jalur absolut dari biner yang dapat dieksekusi dari interpreter Python yang Anda gunakan dan mengekstrak string jalur ke direktori envs eksklusif. Misalnya: Penerjemah python dari conda env saya berada di:

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

Ini bekerja hanya dengan proyek-konda dengan struktur proyek tetap dari proyek anaconda


0

Saya menggunakan metode ../ untuk mengambil jalur proyek saat ini.

Contoh: Project1 - D: \ projects

src

ConfigurationFiles

Configuration.cfg

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


0

Pada saat penulisan, tidak ada solusi lain yang sangat mandiri. Mereka bergantung pada variabel lingkungan atau posisi modul dalam struktur paket. Jawaban teratas dengan solusi 'Django' menjadi korban dari yang terakhir dengan membutuhkan impor relatif. Ini juga memiliki kerugian karena harus memodifikasi modul di tingkat atas.

Ini harus menjadi pendekatan yang tepat untuk menemukan jalur direktori dari paket tingkat atas:

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

Ia bekerja dengan mengambil komponen pertama dalam string bertitik yang ada di dalamnya __name__dan menggunakannya sebagai kunci sys.modulesyang mengembalikan objek modul dari paket tingkat atas. Its __file__atribut berisi jalan yang kita inginkan setelah pemangkasan off /__init__.pymenggunakan os.path.dirname().

Solusi ini berdiri sendiri. Ini berfungsi di mana saja di modul apa pun dari paket, termasuk di __init__.pyfile tingkat atas .


Bisakah Anda menambahkan deskripsi singkat tentang solusi Anda dan bagaimana mereka dapat menggunakannya sebagai solusi?
LuRsT

0

Saya harus menerapkan solusi khusus karena tidak sesederhana yang Anda bayangkan. Solusi saya didasarkan pada pemeriksaan jejak tumpukan ( inspect.stack()) + sys.pathdan berfungsi dengan baik di mana pun lokasi modul python di mana fungsi tersebut dipanggil atau penerjemah (saya mencoba dengan menjalankannya di PyCharm, di shell puisi dan lainnya ... ). Ini adalah implementasi lengkap dengan komentar:

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

Ada banyak jawaban di sini tetapi saya tidak dapat menemukan sesuatu yang sederhana yang mencakup semua kasus, jadi izinkan saya untuk menyarankan solusi saya juga:

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.