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.

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

jrd1
sumber
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).

RikH
sumber
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)
Martim
sumber
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()
DevPlayer
sumber
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.

lihai
sumber
1
Saya suka pilihan band Anda!
dylan_fan
4

Di Bawah Kode Mengembalikan jalur sampai root proyek Anda

import sys
print(sys.path[1])
Arpan Saini
sumber
Tip yang bagus! Saya bertanya-tanya mengapa tidak ada yang menyukai jawaban Anda kecuali saya: P
daveoncode
Terima kasih Daveon Sangat menghargai itu !!
Arpan Saini
Sayangnya bukan itu, sederhana: P ... lihat solusi lengkap saya: stackoverflow.com/a/62510836/267719
daveoncode
3

Mencoba:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
harry
sumber
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')
Orang
sumber
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)
Alex Granovsky
sumber
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])
Gaz_Edge
sumber
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

Joseph Burnitz
sumber
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

Domsch
sumber
0

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

Contoh: Project1 - D: \ projects

src

ConfigurationFiles

Configuration.cfg

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

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

Pyprohly
sumber
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
daveoncode
sumber
-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

alonhzn.dll
sumber