Python - Kök proje yapısının yolunu bul


127

Proje kök dizininde bir yapılandırma dosyası olan bir python projem var. Konfigürasyon dosyasına proje boyunca birkaç farklı dosyadan erişilmesi gerekir.

: Yani şöyle görünür <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(b, a.py erişim yapılandırma dosyası).

İçinde bulunduğum projenin içindeki hangi dosyaya bağlı olmadan proje köküne ve yapılandırma dosyasına giden yolu almanın en iyi / en kolay yolu nedir? yani kullanmadan ../../? Proje kökünün adını bildiğimizi varsayabiliriz.


mu <ROOT>/__init__.pyvar?
mgilson

Yapılandırma dosyanız bir python modülüdür ve sadece bir import ifadesiyle kolayca erişebilirsiniz, ya bir python modülü değildir ve onu iyi bilinen bir yere koymalısınız. Örneğin $ HOME / .projem / projem.conf.
John Smith İsteğe Bağlı

@JohnSmithOptional - Bir JSON dosyasıdır. Yolu kullanarak ona erişebilmem gerekiyor. Evet. Tüm klasörler onu içerir.
Shookie

_ Proje kökünün adını bildiğimizi varsaymakta sorun yok._ Bu, projenin yolunu bildiğiniz anlamına mı geliyor? Öyleyse sadece os.path.join (bilinen_root_name, "configuration.conf") değil mi?
tdelaney

Bir kullanıcı yapılandırmasıysa, genellikle şöyle bir şey kullanırdım os.path.expanduser('~/.myproject/myproject.conf'). Unix ve Windows üzerinde çalışır.
John Smith İsteğe Bağlı

Yanıtlar:


158

Bunu Django'nun yaptığı gibi yapabilirsiniz: Projenin en üst düzeyindeki bir dosyadan Proje Köküne bir değişken tanımlayın. Örneğin, proje yapınız böyle görünüyorsa:

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

İçinde definitions.pytanımlayabilirsiniz (bu gerektirir import os):

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

Böylece, bilinen Proje Kökü ile konfigürasyonun konumuna işaret eden bir değişken oluşturabilirsiniz (bu herhangi bir yerde tanımlanabilir, ancak mantıksal bir yer, sabitlerin tanımlandığı bir konuma koymak olabilir - örneğin definitions.py):

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

Sonra, kolayca ithalat açıklamada (örneğin içinde olan (diğer dosyaların herhangi birinde) sabiti erişebilirsiniz utils.py): from definitions import CONFIG_PATH.


1
Definitions.py dosyasını bu şekilde dahil etmek __init__.pyiçin, kök proje dizinine de bir dosya eklemek gerekecek mi? Bu doğru olmalı mı? Python ile yeni başladım ve en iyi uygulamalardan emin değilim. Teşekkürler.
akskap

3
Bir Hayır: @akskap __init__.pypaketleri tanımlarken o dosya sadece gerekli olduğu kadar gerekli olmayacak: dosyalar Python paketlerini içeren dizinler olarak algılaması için gereklidir; bu, dize gibi ortak bir ada sahip dizinlerin, daha sonra modül arama yolunda ortaya çıkan geçerli modülleri istemeden gizlemesini önlemek için yapılır. En basit durumda, yalnızca boş bir dosya olabilir, ancak paket için başlatma kodunu da çalıştırabilir veya daha sonra açıklanacak değişkeni ayarlayabilir . Bakınız: docs.python.org/3/tutorial/modules.html#packages__init__.py__init__.py__all__
jrd1

Bu tanımları __init.py__kök paketine eklemenin kabul edilebilir olup olmadığını merak ediyorum, üslup açısından bakıyorum . Başka bir dosya oluşturmayı kaydeder ve daha güzel sözdizimine izin verir from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6

@ Johndt6: kongre __init__.pyboş tutmaktır , ancak bu kesinlikle doğru değildir (sonuçta bu bir kongre). Daha fazlası için buna bakın: stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor: hayır - belirttiğiniz örnekte, os.path.abspathbir dizeyi çağırıyor '__file__',. Bunun __file__aslında Python modülleri için tanımlanmış bir içe aktarma özelliği olduğunu hatırlayın . Bu durumda, __file__modülün yüklendiği yol adını döndürür. Daha fazlasını buradan okuyun (modüller bölümüne bakın): docs.python.org/3/reference/datamodel.html
jrd1

62

Diğer cevaplar, projenin en üst seviyesinde bir dosya kullanma tavsiyesi. pathlib.PathVe parent(Python 3.4 ve üstü) kullanıyorsanız bu gerekli değildir . Dışındaki tüm dosyaları aşağıdaki dizin yapısını düşünün README.mdve utils.pyatlanmıştır.

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

İçinde utils.pyaşağıdaki işlevi tanımlıyoruz.

from pathlib import Path

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

Projedeki herhangi bir modülde artık proje kökünü aşağıdaki gibi alabiliriz.

from src.utils import get_project_root

root = get_project_root()

Yararları : get_project_rootProgram davranışını değiştirmeden, çağıran herhangi bir modül taşınabilir. Yalnızca modül utils.pytaşındığında güncellememiz get_project_rootve içe aktarmamız gerekir (bunu otomatikleştirmek için yeniden düzenleme araçları kullanılabilir).


2
Kökte bulunan herhangi bir modül. Src.utils'i kökün dışından çağırmak işe yaramaz. Yanlış mıyım?
aerijman

' dosya ' adı tanımlı değil, neden?
Luk Aron

26

Önceki tüm çözümler, ihtiyacın olduğunu düşündüğüm şey için aşırı derecede karmaşık görünüyor ve çoğu zaman benim için işe yaramadı. Aşağıdaki tek satırlık komut istediğinizi yapar:

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

3
Bunu config.py'ye, dizinin köküne koyun .. bamn! Kendine bir tekil aldın.
swdev

2
Bu yöntem, uygulamayı var olduğu yolun içinden çalıştırdığınızı varsayar. Birçok "kullanıcının" bir masaüstünden tıkladıkları bir simgesi vardır veya uygulamayı tamamen başka bir dizinden çalıştırabilir.
DevPlayer

23

"Kök" modülün yolunu almak için şunları kullanabilirsiniz:

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

Ancak daha da ilginci, en üst modülünüzde okuyabileceğiniz bir yapılandırma "nesnesi" varsa, şu şekilde:

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

1
Burada osvarsayılan olarak mevcut değildir. İçe aktarılması gerekiyor os. Dolayısıyla, satırı eklemek import oscevabı daha eksiksiz hale getirir.
Md. Abu Nafee Ibna Zahid

5
Bu, çalıştırılan betiği içeren dizini verir. Örneğin, çalıştırırken yerine python3 -m topmodule.submodule.scriptverecektir . /path/to/topmodule/submodule/path/to/topmodule
danijar

14

Bunu başarmanın standart bir yolu pkg_resources, programın parçası olan modülü kullanmaktır .setuptools paketin bir . setuptoolskurulabilir bir python paketi oluşturmak için kullanılır.

Sen kullanabilirsiniz pkg_resourcesbir dize olarak istenen dosyanın içeriğini döndürmek için ve kullanabileceğinizpkg_resources gerçek yolunu sisteminizde almak için .

Diyelim ki adlı bir paketiniz var stackoverflow.

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

3 directories, 7 files

Şimdi Rush dosyasına bir modülden erişmek istediğinizi varsayalım app.run. Kullanım pkg_resources.resouces_filenameyolunu Rush ve almak için pkg_resources.resource_stringRush içeriğini almak için; thusly:

import pkg_resources

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

Çıktı:

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

Bu, python yolunuzdaki tüm paketler için çalışır. Dolayısıyla lxml.etree, sisteminizde nerede olduğunu bilmek istiyorsanız :

import pkg_resources

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

çıktı:

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

Buradaki önemli nokta, sisteminize yüklenmiş dosyalara (örneğin, pip install xxx veya yum -y install python-xxx) ve şu anda üzerinde çalıştığınız modülün içindeki dosyalara erişmek için bu standart yöntemi kullanabilmenizdir.


1
Grup seçimini beğendim!
dylan_fan

4

Kodun Altında Proje köküne kadar olan yolu döndürür

import sys
print(sys.path[1])

Güzel ipucu! Cevabınızı neden benden başka kimse yükseltmedi merak ediyorum: P
daveoncode

Teşekkürler Daveon Gerçekten minnettarım !!
Arpan Saini

Maalesef o kadar basit değil: P ... tam
çözümüme

3

Deneyin:

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

1
Bu tam olarak ihtiyacım olan şey. Basit çözüm, benim için işe yarıyor çünkü yapım root-> config-> conf.py. Conf.py'de proje kökünü tanımlamak istedim ve root bu dosyadan tam olarak iki seviye yukarıdaydı.
Daniyal Arshad

2

Ben de bu çözüme gelene kadar bu sorunla mücadele ettim. Bu bence en temiz çözüm.

Setup.py dosyanıza "paketleri" ekleyin

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

Senin içinde 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')

Sanal bir ortam kullanmak ve python3 setup.py installonunla birlikte paketi kurmak artık kaynak kod klasörüne değil, içindeki yumurtaya işaret ediyordu ~./virtualenv/..../app.egg. Bu yüzden konfigürasyon dosyasını paket kurulumuna dahil etmem gerekti.
loxosceles

2

Sadece bir örnek: Ben çalıştırmak istediğiniz runio.py içinden helper1.py

Proje ağacı örneği:

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

Proje kökünü alın:

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

Komut dosyasına yol oluşturun:

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

1

Bu, proje kök dizini altındaki sanal ortamımla (venv) standart bir PyCharm projesi kullanarak benim için çalıştı.

Aşağıdaki kod en güzel değildir, ancak sürekli olarak proje kökünü alır. VIRTUAL_ENVOrtam değişkeninden venv'ye tam dizin yolunu döndürür, örn./Users/NAME/documents/PROJECT/venv

Daha sonra yolu en sonunda böler ve /iki elemanlı bir dizi verir. İlk öğe proje yolu olacaktır, örn./Users/NAME/documents/PROJECT

import os

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

3
Bu, anaconda veya pipenv gibi kurulumlarda çalışmaz, çünkü bu durumlarda sanal ortam proje içinde yer almaz.
Gripp

1

Son zamanlarda benzer bir şey yapmaya çalışıyorum ve bu cevapları kullanım durumlarım için yetersiz buldum (proje kökünü tespit etmesi gereken dağıtılmış bir kitaplık). Temelde farklı ortamlar ve platformlarla savaşıyorum ve hala mükemmel evrensel bir şey bulamadım.

Projeye yerel kodlama

Bu örnekten Django vb. Yerlerde bahsedildiğini ve kullanıldığını gördüm.

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

Bu kadar basit, yalnızca parçacığın içinde bulunduğu dosya aslında projenin bir parçası olduğunda çalışır. Proje dizinini değil, kod parçacığının dizinini alıyoruz

Benzer şekilde, sys.modules yaklaşımı , uygulamanın giriş noktasının dışından çağrıldığında bozuluyor , özellikle bir çocuk iş parçacığının bunu ' ana ' modülle ilişkisi olmadan belirleyemediğini gözlemledim . Bir alt iş parçacığından içe aktarmayı göstermek için içe aktarmayı açıkça bir işlevin içine koydum, onu app.py'nin en üst düzeyine taşımak sorunu düzeltecektir.

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

Bu programı çalıştırmak bir öznitelik hatası oluşturur:

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

... dolayısıyla iş parçacığı tabanlı bir çözüm

Yerden bağımsız

Öncekiyle aynı uygulama yapısını kullanıp ayarları değiştirerek.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))

Bunu parçalamak: Önce ana iş parçacığının iş parçacığı kimliğini doğru bir şekilde bulmak istiyoruz. Python3.4 + 'da iş parçacığı kitaplığı vardır, threading.main_thread()ancak herkes 3.4+ kullanmaz, bu nedenle kimliğini kaydetmeden ana iş parçacığını arayan tüm iş parçacıklarını ararız. Ana ileti dizisinden zaten çıkılmışsa, içinde listelenmeyecektir threading.enumerate(). RuntimeError()Ben daha iyi bir çözüm bulana kadar bu durumda a yükseltiyoruz .

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

Daha sonra ana iş parçacığının ilk yığın çerçevesini buluyoruz. CPython'a özgü işlevi kullanarak, sys._current_frames() her iş parçacığının mevcut yığın çerçevesinin bir sözlüğünü elde ederiz. Daha sonra kullanarak inspect.getouterframes()ana iş parçacığı ve ilk çerçeve için tüm yığını alabiliriz. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Son olarak, Windows ve Linux uygulamaları arasındaki farkların inspect.getouterframes()ele alınması gerekir. Temizlenmiş dosya adını kullanma os.path.abspath()veos.path.dirname() her şeyi temizleyin.

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

Şimdiye kadar bunu Windows'ta Python2.7 ve 3.6'da ve WSL'de Python3.4'te test ettim


0

Anaconda-project ile çalışıyorsanız, PROJECT_ROOT'u -> os.getenv ('PROJECT_ROOT') ortam değişkeninden sorgulayabilirsiniz. Bu, yalnızca komut dosyası anaconda-project run aracılığıyla yürütülürse çalışır.

Betiğinizin anaconda-project tarafından çalıştırılmasını istemiyorsanız, kullandığınız Python yorumlayıcısının çalıştırılabilir ikili dosyasının mutlak yolunu sorgulayabilir ve yol dizesini envs dizini exclusiv'e çıkartabilirsiniz. Örneğin: conda env'imin python yorumlayıcısı şu adreste bulunur:

/ Home / kullanıcı / 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]

Bu sadece bir anaconda projesinin sabit proje yapısına sahip conda-project ile çalışır.


0

Mevcut proje yolunu getirmek için ../ yöntemini kullandım.

Örnek: Proje1 - D: \ projeler

src

ConfigurationFiles

Configuration.cfg

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


0

Yazma sırasında, diğer çözümlerin hiçbiri çok bağımsız değildir. Ya bir ortam değişkenine ya da modülün paket yapısındaki konumuna bağlıdırlar. 'Django' çözümünün en iyi yanıtı, göreceli bir içe aktarım gerektirerek ikincisinin kurbanı oluyor. Ayrıca, bir modülü en üst düzeyde modifiye etme dezavantajına da sahiptir.

Bu, üst düzey paketin dizin yolunu bulmak için doğru yaklaşım olmalıdır:

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

İçerdiği noktalı dizedeki ilk bileşeni alarak __name__ve onu sys.modulesüst düzey paketin modül nesnesini döndüren bir anahtar olarak kullanarak çalışır . Onun __file__nitelik kapalı kırparak sonra istediğimiz yolu içerir /__init__.pykullanarak os.path.dirname().

Bu çözüm bağımsızdır. Üst düzey __init__.pydosya dahil paketin herhangi bir modülünde herhangi bir yerde çalışır .


Çözümünüz ve çözümü olarak nasıl kullanabilecekleri hakkında kısa bir açıklama ekleyebilir misiniz?
LuRsT

0

Düşündüğünüz kadar basit olmadığı için özel bir çözüm uygulamak zorunda kaldım. Benim çözümüm yığın izleme denetimine ( inspect.stack()) + dayanıyor sys.pathve işlevin çağrıldığı python modülünün konumu ne olursa olsun veya yorumlayıcı (PyCharm'da, şiir kabuğunda ve diğerlerinde çalıştırmayı denedim ... ). Bu, yorumlarla birlikte tam bir uygulamadır:

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

Burada birçok cevap var, ancak tüm vakaları kapsayan basit bir şey bulamadım, bu yüzden çözümümü de önermeme izin verin:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.