Python - Nhận đường dẫn của cấu trúc dự án gốc


126

Tôi đã có một dự án python với một tập tin cấu hình trong thư mục gốc của dự án. Tệp cấu hình cần được truy cập trong một vài tệp khác nhau trong suốt dự án.

Vì vậy, nó trông giống như : <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(khi b, a.py truy cập tệp cấu hình).

Cách tốt nhất / dễ nhất để có được đường dẫn đến gốc dự án và tệp cấu hình mà không phụ thuộc vào tệp nào trong dự án tôi đang ở? tức là không dùng ../../? Sẽ ổn khi cho rằng chúng ta biết tên gốc của dự án.


<ROOT>/__init__.pytồn tại không?
mgilson

Tệp cấu hình của bạn là mô-đun python và bạn có thể dễ dàng truy cập nó chỉ bằng một câu lệnh nhập, đó không phải là mô-đun python và bạn nên đặt nó ở một vị trí nổi tiếng. Ví dụ: $ HOME / .my_project / my_project.conf.
John Smith Tùy chọn

@JohnSmithOptional - Đó là một tệp JSON. Tôi cần có thể truy cập nó bằng cách sử dụng đường dẫn. Đúng. Tất cả các thư mục bao gồm nó.
Shookie

_ Sẽ ổn khi cho rằng chúng tôi biết tên gốc của dự án._ Điều đó có nghĩa là bạn biết đường dẫn đến dự án? Không phải chỉ là os.path.join (đã biết_root_name, "configure.conf") sao?
tdelaney

Nếu đó là cấu hình người dùng, tôi thường sử dụng cái gì đó như os.path.expanduser('~/.myproject/myproject.conf'). Nó hoạt động trên Unix và Windows.
John Smith Tùy chọn

Câu trả lời:


157

Bạn có thể làm điều này như thế nào Django làm điều đó: xác định một biến cho Project Root từ một tệp nằm ở cấp cao nhất của dự án. Ví dụ, nếu đây là cấu trúc dự án của bạn trông như thế nào:

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

Trong definitions.pybạn có thể định nghĩa (yêu cầu này import os):

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

Do đó, với Project Root đã biết, bạn có thể tạo một biến trỏ đến vị trí của cấu hình (điều này có thể được xác định ở bất cứ đâu, nhưng một vị trí hợp lý sẽ được đặt ở vị trí nơi các hằng số được xác định - ví dụ definitions.py):

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

Sau đó, bạn có thể dễ dàng truy cập vào hằng số (trong bất kỳ tệp nào khác) bằng câu lệnh nhập (ví dụ: in utils.py) : from definitions import CONFIG_PATH.


1
Để bao gồm các tệp định nghĩa như vậy, nó có được yêu cầu thêm một __init__.pytệp vào thư mục dự án gốc không? Điều đó có đúng không? Tôi mới bắt đầu với python và không chắc chắn về các thực hành tốt nhất. Cảm ơn.
akskap

3
@akskap: Không, __init__.pysẽ không bắt buộc, vì tệp đó chỉ được yêu cầu khi xác định gói: Các __init__.pytệp được yêu cầu để Python xử lý các thư mục là chứa các gói; điều này được thực hiện để ngăn các thư mục có tên chung, chẳng hạn như chuỗi, vô tình che giấu các mô-đun hợp lệ xảy ra sau này trên đường dẫn tìm kiếm mô-đun. Trong trường hợp đơn giản nhất, __init__.pycó thể chỉ là một tệp trống, nhưng nó cũng có thể thực thi mã khởi tạo cho gói hoặc đặt __all__biến, được mô tả sau. Xem: docs.python.org/3/tutorial/modules.html#packages
jrd1

Tôi tò mò, phong cách khôn ngoan, cho dù có thể chấp nhận hay nhăn mặt khi thêm các định nghĩa này vào __init.py__gói gốc. Nó sẽ lưu tạo một tệp khác, cũng như cho phép cú pháp đẹp hơn from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6

@ Johndt6: quy ước là để __init__.pytrống, nhưng điều đó không hoàn toàn đúng ( rốt cuộc đó là một quy ước). Xem phần này để biết thêm: stackoverflow.com/questions/2361124/USE-init-py
jrd1

1
@JavNoor: không - trong ví dụ bạn đã trích dẫn, os.path.abspathđang gọi một chuỗi , '__file__'. Nhớ lại rằng đó __file__thực sự là một thuộc tính nhập khẩu được xác định cho các mô-đun Python. Trong trường hợp này, __file__sẽ trả về tên đường dẫn mà mô-đun được tải. Đọc thêm ở đây (xem phần module): docs.python.org/3/reference/datamodel.html
jrd1

61

Lời khuyên trả lời khác để sử dụng một tập tin ở cấp cao nhất của dự án. Điều này là không cần thiết nếu bạn sử dụng pathlib.Pathparent(Python 3.4 trở lên). Xem xét cấu trúc thư mục sau trong đó tất cả các tệp ngoại trừ README.mdutils.pyđã bị bỏ qua.

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

Trong utils.pychúng tôi xác định các chức năng sau đây.

from pathlib import Path

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

Trong bất kỳ mô-đun nào trong dự án, bây giờ chúng ta có thể lấy gốc của dự án như sau.

from src.utils import get_project_root

root = get_project_root()

Lợi ích : Bất kỳ mô-đun nào get_project_rootcó thể được di chuyển mà không thay đổi hành vi chương trình. Chỉ khi mô-đun utils.pyđược di chuyển, chúng tôi phải cập nhật get_project_rootvà nhập (công cụ tái cấu trúc có thể được sử dụng để tự động hóa việc này).


2
Bất kỳ mô-đun nào trong thư mục gốc. Gọi src.utils từ bên ngoài root không nên hoạt động. Liệu tôi có sai?
aerijman

Tên ' tập tin ' không được xác định, tại sao?
Luk Aron

26

Tất cả các giải pháp trước đây dường như quá phức tạp đối với những gì tôi nghĩ bạn cần và thường không hiệu quả với tôi. Lệnh một dòng sau đây thực hiện những gì bạn muốn:

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

3
Đặt nó trong config.py, ở thư mục gốc của thư mục, .. bamn! Yout có cho mình một singleton.
swdev

1
Phương pháp này cho rằng bạn chạy ứng dụng từ bên trong đường dẫn mà nó tồn tại. Nhiều "người dùng" có biểu tượng họ nhấp từ máy tính để bàn hoặc có thể chạy ứng dụng từ thư mục khác hoàn toàn.
DevPlayer

23

Để có được đường dẫn của mô-đun "root", bạn có thể sử dụng:

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

Nhưng thú vị hơn nếu bạn có một "đối tượng" cấu hình trong mô-đun hàng đầu của bạn, bạn có thể đọc - từ đó như sau:

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

1
Ở đây oskhông có mặc định có sẵn. Cần nhập hàng os. Vì vậy, thêm dòng import ossẽ làm cho câu trả lời đầy đủ hơn.
Md. Abu Nafee Ibna Zahid

4
Điều này cung cấp cho thư mục chứa tập lệnh đã được thực thi. Ví dụ, khi chạy python3 -m topmodule.submodule.scriptnó sẽ cung cấp /path/to/topmodule/submodulethay vì /path/to/topmodule.
danijar

14

Một cách tiêu chuẩn để đạt được điều này sẽ là sử dụng pkg_resourcesmô-đun là một phần của setuptoolsgói. setuptoolsđược sử dụng để tạo một gói python có thể cài đặt.

Bạn có thể sử dụng pkg_resourcesđể trả về nội dung của tệp mong muốn dưới dạng chuỗi và bạn có thể sử dụng pkg_resourcesđể nhận đường dẫn thực tế của tệp mong muốn trên hệ thống của mình.

Hãy nói rằng bạn có một gói được gọi stackoverflow.

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

3 directories, 7 files

Bây giờ hãy nói rằng bạn muốn truy cập tệp Rush từ một mô-đun app.run. Sử dụng pkg_resources.resouces_filenameđể có được đường dẫn đến Rush và pkg_resources.resource_stringđể lấy nội dung của Rush; do đó:

import pkg_resources

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

Đầu ra:

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

Điều này làm việc cho tất cả các gói trong đường dẫn python của bạn. Vì vậy, nếu bạn muốn biết nơi lxml.etreetồn tại trên hệ thống của bạn:

import pkg_resources

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

đầu ra:

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

Vấn đề là bạn có thể sử dụng phương pháp tiêu chuẩn này để truy cập các tệp được cài đặt trên hệ thống của mình (ví dụ: pip install xxx hoặc yum -y install python-xxx) và các tệp nằm trong mô-đun mà bạn hiện đang làm việc.


1
Tôi thích sự lựa chọn ban nhạc của bạn!
dylan_fan

3

Thử:

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

1
Đây chính xác là những gì tôi cần. Giải pháp đơn giản, phù hợp với tôi vì cấu trúc của tôi là root-> config-> conf.py Tôi muốn xác định gốc dự án trong conf.py và root chính xác là hai cấp độ từ tệp đó.
Daniyal Arshad

3

Mã bên dưới Trả về đường dẫn cho đến khi gốc dự án của bạn

import sys
print(sys.path[1])

Mẹo hay! Tôi tự hỏi tại sao không ai đánh giá cao câu trả lời của bạn ngoài tôi: P
daveoncode

Cảm ơn Daveon thực sự đánh giá cao điều đó !!
Arpan Saini

Thật không may là không phải vậy, đơn giản: P ... hãy xem giải pháp đầy đủ của tôi: stackoverflow.com/a/62510836/267719
daveoncode

2

Tôi đã vật lộn với vấn đề này cho đến khi tôi đi đến giải pháp này. Đây là giải pháp sạch nhất theo ý kiến ​​của tôi.

Trong bạn setup.py add "gói"

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

Trong python_script.py của bạn

import pkg_resources
import os

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

Sử dụng một môi trường ảo và cài đặt gói với python3 setup.py installnó không chỉ vào thư mục mã nguồn nữa mà là vào quả trứng bên trong ~./virtualenv/..../app.egg. Vì vậy, tôi phải đưa tập tin cấu hình vào gói cài đặt.
loxosceles

2

Chỉ là một ví dụ: Tôi muốn chạy runio.py từ trong helper1.py

Ví dụ cây dự án:

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

Lấy gốc dự án:

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

Xây dựng đường dẫn đến kịch bản:

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

1

Điều này làm việc cho tôi bằng cách sử dụng một dự án PyCharm tiêu chuẩn với môi trường ảo (venv) của tôi trong thư mục gốc của dự án.

Mã dưới đây không phải là đẹp nhất, nhưng luôn luôn có được gốc dự án. Nó trả về đường dẫn thư mục đầy đủ đến venv từ VIRTUAL_ENVbiến môi trường, vd/Users/NAME/documents/PROJECT/venv

Sau đó, nó chia đường dẫn cuối cùng /, đưa ra một mảng có hai phần tử. Phần tử đầu tiên sẽ là đường dẫn dự án, vd/Users/NAME/documents/PROJECT

import os

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

3
Điều này sẽ không hoạt động với các thiết lập như anaconda hoặc pipenv, vì môi trường ảo không có trong dự án trong những trường hợp đó.
Nắm chặt

1

Gần đây tôi đã cố gắng làm một cái gì đó tương tự và tôi đã tìm thấy những câu trả lời này không thỏa đáng cho các trường hợp sử dụng của tôi (một thư viện phân tán cần phát hiện root dự án). Chủ yếu là tôi đã chiến đấu với các môi trường và nền tảng khác nhau, và vẫn chưa tìm thấy thứ gì đó hoàn toàn phổ biến.

Mã địa phương để dự án

Tôi đã thấy ví dụ này được đề cập và sử dụng ở một vài nơi, Django, v.v.

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

Đơn giản như vậy, nó chỉ hoạt động khi tệp mà đoạn mã nằm trong thực sự là một phần của dự án. Chúng tôi không truy xuất thư mục dự án, mà thay vào đó là thư mục của đoạn trích

Tương tự, cách tiếp cận sys.modules bị hỏng khi được gọi từ bên ngoài điểm vào của ứng dụng, cụ thể tôi đã quan sát thấy một luồng con có thể xác định điều này mà không liên quan đến mô đun ' chính '. Tôi đã đặt rõ ràng việc nhập vào bên trong một hàm để thể hiện việc nhập từ một luồng con, chuyển nó sang cấp cao nhất của app.py sẽ khắc phục nó.

app/
|-- config
|   `-- __init__.py
|   `-- settings.py
`-- app.py

ứng dụng

#!/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

cài đặt

import os
import sys


ROOT_DIR = None


def setup():
    global ROOT_DIR
    ROOT_DIR = os.path.dirname(sys.modules['__main__'].__file__)
    # Do something slow

Chạy chương trình này tạo ra một lỗi thuộc tính:

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

... do đó, một giải pháp dựa trên luồng

Vị trí độc lập

Sử dụng cấu trúc ứng dụng giống như trước đây nhưng sửa đổi cài đặt

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

Phá vỡ điều này: Đầu tiên chúng tôi muốn tìm chính xác ID luồng của luồng chính. threading.main_thread()Tuy nhiên, trong Python3.4 + thư viện phân luồng có , mọi người không sử dụng 3,4+ nên chúng tôi tìm kiếm trong tất cả các luồng tìm kiếm luồng chính lưu ID của nó. Nếu luồng chính đã thoát, nó sẽ không được liệt kê trong threading.enumerate(). Chúng tôi nêu ra một RuntimeError()trường hợp này cho đến khi tôi tìm thấy một giải pháp tốt hơn.

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

Tiếp theo chúng ta tìm khung stack đầu tiên của luồng chính. Sử dụng chức năng cụ thể của cPython, sys._current_frames() chúng tôi có được một từ điển của khung ngăn xếp hiện tại của mỗi luồng. Sau đó, sử dụng inspect.getouterframes()chúng ta có thể lấy toàn bộ ngăn xếp cho luồng chính và khung đầu tiên. current_main_frame = sys._civerse_frames () [main_id] base_frame = notify.getouterframes (current_main_frame) [- 1] Cuối cùng, inspect.getouterframes()cần phải xử lý các khác biệt giữa việc triển khai Windows và Linux . Sử dụng tên tệp đã được dọn sạch os.path.abspath()os.path.dirname()dọn dẹp mọi thứ.

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

Cho đến nay tôi đã thử nghiệm điều này trên Python2.7 và 3.6 trên Windows cũng như Python3.4 trên WSL


0

Nếu bạn đang làm việc với dự án anaconda, bạn có thể truy vấn PRO DỰ_ROOT từ biến môi trường -> os.getenv ('PRO DỰ_ROOT'). Điều này chỉ hoạt động nếu tập lệnh được thực thi thông qua chạy dự án anaconda.

Nếu bạn không muốn tập lệnh của mình được chạy bởi dự án anaconda, bạn có thể truy vấn đường dẫn tuyệt đối của tệp nhị phân thực thi của trình thông dịch Python mà bạn đang sử dụng và trích xuất chuỗi đường dẫn lên loại trừ thư mục envs. Ví dụ: Trình thông dịch python của conda env của tôi được đặt tại:

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

Điều này chỉ hoạt động với dự án conda với cấu trúc dự án cố định của dự án anaconda


0

Tôi đã sử dụng phương thức ../ để tìm nạp đường dẫn dự án hiện tại.

Ví dụ: Project1 - D: \ dự án

src

Cấu hình tệp

Cấu hình.cfg

Đường dẫn = "../ src / Cấu hình tệp / Cấu hình.cfg"


0

Tại thời điểm viết, không có giải pháp nào khác là rất khép kín. Chúng phụ thuộc vào một biến môi trường hoặc vị trí của mô-đun trong cấu trúc gói. Câu trả lời hàng đầu với giải pháp 'Django trở thành nạn nhân của câu hỏi sau bằng cách yêu cầu nhập tương đối. Nó cũng có nhược điểm là phải sửa đổi một mô-đun ở cấp cao nhất.

Đây phải là cách tiếp cận chính xác để tìm đường dẫn thư mục của gói cấp cao nhất:

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

Nó hoạt động bằng cách lấy thành phần đầu tiên trong chuỗi chấm có trong __name__và sử dụng nó làm khóa trong sys.modulesđó trả về đối tượng mô-đun của gói cấp cao nhất. __file__Thuộc tính của nó chứa đường dẫn chúng ta muốn sau khi cắt xén /__init__.pybằng cách sử dụngos.path.dirname() .

Giải pháp này là khép kín. Nó hoạt động ở bất cứ đâu trong bất kỳ mô-đun nào của gói, kể cả trong __init__.pytệp cấp cao nhất .


Bạn có thể thêm một mô tả ngắn về giải pháp của bạn và làm thế nào họ có thể sử dụng nó làm giải pháp của họ?
LuRsT

0

Tôi đã phải thực hiện một giải pháp tùy chỉnh vì nó không đơn giản như bạn nghĩ. Giải pháp của tôi dựa trên kiểm tra theo dõi ngăn xếp ( inspect.stack()) + sys.pathvà đang hoạt động tốt bất kể vị trí của mô-đun python trong đó chức năng được gọi cũng như trình thông dịch (Tôi đã thử bằng cách chạy nó trong PyCharm, trong một vỏ thơ và ... ). Đây là thực hiện đầy đủ với ý kiến:

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

Có nhiều câu trả lời ở đây nhưng tôi không thể tìm thấy một cái gì đó đơn giản bao gồm tất cả các trường hợp vì vậy cho phép tôi đề xuất giải pháp của mình:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.