Python - รับเส้นทางของโครงสร้างโปรเจ็กต์รูท


128

ฉันมีโปรเจ็กต์ python ที่มีไฟล์คอนฟิกูเรชันในรูทโปรเจ็กต์ ไฟล์คอนฟิกูเรชันจะต้องเข้าถึงได้ในไฟล์ต่างๆสองสามไฟล์ทั่วทั้งโปรเจ็กต์

ดูเหมือนว่า: <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(เมื่อ b, a.py เข้าถึงไฟล์กำหนดค่า)

วิธีใดดีที่สุด / ง่ายที่สุดในการรับเส้นทางไปยังรูทโปรเจ็กต์และไฟล์คอนฟิกูเรชันโดยไม่ขึ้นอยู่กับไฟล์ภายในโปรเจ็กต์ที่ฉันอยู่ เช่นโดยไม่ต้องใช้../../? สมมติว่าเรารู้จักชื่อรูทของโปรเจ็กต์แล้ว


ไม่มี<ROOT>/__init__.pyอยู่จริง?
mgilson

ไฟล์คอนฟิกูเรชันของคุณเป็นโมดูล python และคุณสามารถเข้าถึงได้อย่างง่ายดายด้วยคำสั่ง import ไม่ว่าจะเป็นโมดูล python และคุณควรวางไว้ในตำแหน่งที่รู้จักกันดี ตัวอย่างเช่น $ HOME / .my_project / my_project.conf
John Smith Optional

@JohnSmithOptional - เป็นไฟล์ JSON ฉันต้องสามารถเข้าถึงได้โดยใช้เส้นทางนี้ ใช่. โฟลเดอร์ทั้งหมดรวมไว้ด้วย
Shookie

_ สมมติว่าเรารู้ชื่อรูทของโปรเจ็กต์แล้ว _ หมายความว่าคุณรู้เส้นทางไปยังโปรเจ็กต์หรือไม่ ไม่ใช่แค่ os.path.join (known_root_name, "configuration.conf") งั้นหรือ
tdelaney

os.path.expanduser('~/.myproject/myproject.conf')ถ้ามันเป็นการกำหนดค่าใช้ฉันมักจะใช้สิ่งที่ต้องการ ทำงานบน Unix และ Windows
John Smith Optional

คำตอบ:


159

คุณสามารถทำได้ในลักษณะที่ Django ทำได้: กำหนดตัวแปรให้กับ Project Root จากไฟล์ที่อยู่ในระดับบนสุดของโปรเจ็กต์ ตัวอย่างเช่นหากนี่คือลักษณะโครงสร้างโครงการของคุณ:

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

ในdefinitions.pyคุณสามารถกำหนด (ซึ่งจะต้องimport os):

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

ดังนั้นเมื่อทราบProject Rootแล้วคุณสามารถสร้างตัวแปรที่ชี้ไปยังตำแหน่งของการกำหนดค่าได้ (สามารถกำหนดได้ทุกที่ แต่สถานที่เชิงตรรกะจะต้องวางไว้ในตำแหน่งที่มีการกำหนดค่าคงที่ - เช่นdefinitions.py):

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

จากนั้นคุณสามารถเข้าถึงคงที่ (ในใด ๆ ของไฟล์อื่น ๆ ) มีคำสั่งนำเข้า (เช่นใน):utils.pyfrom definitions import CONFIG_PATH


1
หากต้องการรวมไฟล์ definitions.py แบบนั้นจำเป็นต้องเพิ่ม__init__.pyไฟล์ลงในไดเร็กทอรีโปรเจ็กต์รูทด้วยหรือไม่ ที่ถูกต้องควรหรือไม่? ฉันเพิ่งเริ่มต้นด้วย python และไม่แน่ใจเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุด ขอบคุณ
akskap

3
@akskap: ไม่เป็น__init__.pyจะไม่ต้องเป็นไฟล์ที่จำเป็นเท่านั้นเมื่อกำหนดแพคเกจ: ไฟล์จะต้องทำให้งูหลามรักษาไดเรกทอรีที่เป็นแพคเกจที่มี; สิ่งนี้ทำเพื่อป้องกันไม่ให้ไดเร็กทอรีที่มีชื่อสามัญเช่นสตริงจากการซ่อนโมดูลที่ถูกต้องโดยไม่ได้ตั้งใจซึ่งเกิดขึ้นในภายหลังบนพา ธ การค้นหาโมดูล ในกรณีที่ง่ายที่สุดอาจเป็นเพียงไฟล์เปล่า แต่ยังสามารถรันโค้ดเริ่มต้นสำหรับแพ็กเกจหรือตั้งค่าตัวแปรตามที่อธิบายไว้ในภายหลัง ดู: docs.python.org/3/tutorial/modules.html#packages__init__.py__init__.py__all__
59

ฉันอยากรู้อยากเห็นสไตล์ที่ชาญฉลาดไม่ว่าจะเป็นที่ยอมรับหรือขมวดคิ้วเมื่อเพิ่มคำจำกัดความเหล่านี้ลงใน__init.py__แพ็กเกจรูท มันจะบันทึกการสร้างไฟล์อื่นรวมทั้งอนุญาตให้ใช้ไวยากรณ์ที่ดีกว่าของfrom root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6

@ Johndt6: การประชุมคือการ__init__.pyเว้นว่างไว้แต่นั่นไม่เป็นความจริงอย่างเคร่งครัด (มันเป็นอนุสัญญาทั้งหมด) ดูข้อมูลเพิ่มเติมได้ที่: stackoverflow.com/questions/2361124/using-init-py
jrd1

1
@JavNoor: ไม่ - ในตัวอย่างที่คุณอ้างถึงos.path.abspathกำลังเรียกสตริง, '__file__'. การเรียกคืนนั้น__file__เป็นแอตทริบิวต์การนำเข้าที่กำหนดไว้สำหรับโมดูล Python ในกรณีนี้__file__จะส่งคืนชื่อพา ธ ที่โหลดโมดูล อ่านเพิ่มเติมที่นี่ (ดูส่วนโมดูล): docs.python.org/3/reference/datamodel.html
jrd1

62

คำแนะนำคำตอบอื่น ๆ เพื่อใช้ไฟล์ในระดับบนสุดของโปรเจ็กต์ ไม่จำเป็นหากคุณใช้pathlib.Pathและparent(Python 3.4 ขึ้นไป) พิจารณาโครงสร้างไดเร็กทอรีต่อไปนี้โดยที่ไฟล์ทั้งหมดยกเว้นREADME.mdและutils.pyถูกละไว้

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

ในutils.pyการที่เรากำหนดฟังก์ชั่นดังต่อไปนี้

from pathlib import Path

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

ในโมดูลใด ๆ ในโครงการตอนนี้เราสามารถรับรูทโปรเจ็กต์ได้ดังนี้

from src.utils import get_project_root

root = get_project_root()

ประโยชน์ : โมดูลใด ๆ ที่get_project_rootสามารถย้ายการโทรได้โดยไม่ต้องเปลี่ยนพฤติกรรมของโปรแกรม เมื่อโมดูลutils.pyถูกย้ายเราต้องอัปเดตget_project_rootและนำเข้าเท่านั้น (สามารถใช้เครื่องมือ refactoring เพื่อทำให้เป็นอัตโนมัติได้)


2
โมดูลใด ๆ ที่อยู่ในรูท ไม่ควรเรียก src.utils จากภายนอกรูท ฉันผิดเหรอ?
aerijman

ไม่ได้กำหนดชื่อ ' ไฟล์ ' เพราะเหตุใด
ลูกศร

26

วิธีแก้ปัญหาก่อนหน้านี้ทั้งหมดดูเหมือนจะซับซ้อนเกินไปสำหรับสิ่งที่ฉันคิดว่าคุณต้องการและมักไม่ได้ผลสำหรับฉัน คำสั่งบรรทัดเดียวต่อไปนี้ทำสิ่งที่คุณต้องการ:

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

3
ใส่ไว้ใน config.py ที่ root ของไดเร็กทอรี .. bamn! Yout มีตัวเองเป็นซิงเกิลตัน
swdev

2
วิธีนี้ถือว่าคุณเรียกใช้แอปพลิเคชันจากภายในเส้นทางที่มีอยู่ "ผู้ใช้" จำนวนมากมีไอคอนที่คลิกจากเดสก์ท็อปหรือสามารถเรียกใช้แอปจากไดเรกทอรีอื่นได้ทั้งหมด
DevPlayer

23

ในการรับเส้นทางของโมดูล "root" คุณสามารถใช้:

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

แต่น่าสนใจยิ่งกว่าถ้าคุณมี config "object" ในโมดูลอันดับต้น ๆ ของคุณคุณสามารถ -read- จากมันได้ดังนี้:

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

1
ที่นี่osไม่สามารถใช้ได้ตามค่าเริ่มต้น ต้องนำเข้าos. ดังนั้นการเพิ่มบรรทัดimport osจะทำให้คำตอบสมบูรณ์ยิ่งขึ้น
Md. Abu Nafee Ibna Zahid

5
สิ่งนี้ทำให้ไดเร็กทอรีที่มีสคริปต์ที่ถูกเรียกใช้งาน ตัวอย่างเช่นเมื่อทำงานpython3 -m topmodule.submodule.scriptก็จะให้แทน/path/to/topmodule/submodule /path/to/topmodule
danijar

14

วิธีมาตรฐานในการบรรลุเป้าหมายนี้คือการใช้pkg_resourcesโมดูลซึ่งเป็นส่วนหนึ่งของsetuptoolsแพ็คเกจ setuptoolsใช้เพื่อสร้างแพ็คเกจ python ที่สามารถติดตั้งได้

คุณสามารถใช้pkg_resourcesเพื่อส่งคืนเนื้อหาของไฟล์ที่คุณต้องการเป็นสตริงและคุณสามารถใช้pkg_resourcesเพื่อรับเส้นทางจริงของไฟล์ที่ต้องการบนระบบของคุณ

stackoverflowพูดเถอะว่าคุณได้แพคเกจที่เรียกว่า

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

3 directories, 7 files

ตอนนี้ขอบอกว่าคุณต้องการที่จะเข้าถึง Rush app.runไฟล์จากโมดูล ใช้pkg_resources.resouces_filenameเพื่อรับเส้นทางสู่ Rush และpkg_resources.resource_stringรับเนื้อหาของ Rush; thusly:

import pkg_resources

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

ผลลัพธ์:

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

สิ่งนี้ใช้ได้กับแพ็คเกจทั้งหมดในเส้นทาง python ของคุณ ดังนั้นหากคุณต้องการทราบว่าlxml.etreeมีอยู่ที่ไหนในระบบของคุณ:

import pkg_resources

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

เอาท์พุท:

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

ประเด็นก็คือคุณสามารถใช้วิธีมาตรฐานนี้เพื่อเข้าถึงไฟล์ที่ติดตั้งในระบบของคุณ (เช่น pip install xxx หรือ yum -y install python-xxx) และไฟล์ที่อยู่ในโมดูลที่คุณกำลังทำงานอยู่


1
ฉันชอบวงดนตรีของคุณ!
dylan_fan

4

ลอง:

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

1
นี่คือสิ่งที่ฉันต้องการ วิธีแก้ปัญหาง่ายๆใช้ได้กับฉันเพราะโครงสร้างของฉันคือ root-> config-> conf.py ฉันต้องการกำหนดรูทโปรเจ็กต์ใน conf.py และรูทมีสองระดับจากไฟล์นั้น
Daniyal Arshad

4

ด้านล่างโค้ดส่งคืนเส้นทางจนกว่าโปรเจ็กต์ของคุณจะรูท

import sys
print(sys.path[1])

เคล็ดลับที่ดี! ฉันสงสัยว่าทำไมไม่มีใครโหวตคำตอบของคุณนอกจากฉัน: P
daveoncode

ขอบคุณ Daveon ขอบคุณจริงๆ !!
Arpan Saini

น่าเสียดายที่ไม่ใช่เรื่องง่าย: P ... ลองดูวิธีแก้ปัญหาแบบเต็มของฉัน: stackoverflow.com/a/62510836/267719
daveoncode

2

ฉันก็ต่อสู้กับปัญหานี้เช่นกันจนกระทั่งฉันได้วิธีแก้ปัญหานี้ นี่เป็นทางออกที่สะอาดที่สุดในความคิดของฉัน

ในsetup.pyของคุณเพิ่ม "แพ็คเกจ"

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

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

การใช้สภาพแวดล้อมเสมือนจริงและติดตั้งแพคเกจที่มีpython3 setup.py installมันก็ไม่ได้ชี้ไปยังโฟลเดอร์รหัสที่มาอีกต่อไป ~./virtualenv/..../app.eggแต่ภายในไข่ ดังนั้นฉันจึงต้องรวมไฟล์คอนฟิกูเรชันลงในการติดตั้งแพ็คเกจ
loxosceles

2

เพียงตัวอย่าง: ฉันต้องการเรียกใช้runio.pyจากภายในhelper1.py

ตัวอย่างแผนผังโครงการ:

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

รับรูทโครงการ:

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

สร้างเส้นทางไปยังสคริปต์:

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

1

สิ่งนี้ใช้ได้ผลสำหรับฉันโดยใช้โครงการ PyCharm มาตรฐานกับสภาพแวดล้อมเสมือนของฉัน (venv) ภายใต้ไดเรกทอรีรากของโครงการ

โค้ดด้านล่างไม่ได้สวยที่สุด แต่ได้รับรูทโปรเจ็กต์อย่างสม่ำเสมอ ส่งคืนพา ธ ไดเร็กทอรีแบบเต็มไปยัง venv จากVIRTUAL_ENVตัวแปรสภาวะแวดล้อมเช่น/Users/NAME/documents/PROJECT/venv

จากนั้นแยกเส้นทางในที่สุด/โดยให้อาร์เรย์ที่มีสององค์ประกอบ องค์ประกอบแรกคือเส้นทางโครงการเช่น/Users/NAME/documents/PROJECT

import os

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

3
สิ่งนี้ใช้ไม่ได้กับการตั้งค่าเช่น anaconda หรือ pipenv เนื่องจากสภาพแวดล้อมเสมือนไม่ได้อยู่ในโครงการในกรณีเหล่านั้น
Gripp

1

เมื่อไม่นานมานี้ฉันพยายามทำสิ่งที่คล้ายกันและพบว่าคำตอบเหล่านี้ไม่เพียงพอสำหรับกรณีการใช้งานของฉัน (ไลบรารีแบบกระจายที่ต้องตรวจหารูทของโปรเจ็กต์) ส่วนใหญ่ฉันต่อสู้กับสภาพแวดล้อมและแพลตฟอร์มที่แตกต่างกันและยังไม่พบสิ่งที่เป็นสากลอย่างสมบูรณ์แบบ

รหัสท้องถิ่นให้กับโครงการ

ฉันเคยเห็นตัวอย่างนี้พูดถึงและใช้ในสถานที่บางแห่ง Django ฯลฯ

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

ง่ายๆก็คือจะใช้งานได้ก็ต่อเมื่อไฟล์ที่มีข้อมูลโค้ดนั้นเป็นส่วนหนึ่งของโปรเจ็กต์จริงๆ เราไม่ดึงข้อมูลไดเร็กทอรีโครงการ แต่ใช้ไดเร็กทอรีของ snippet แทน

ในทำนองเดียวกันวิธีการsys.modulesจะพังลงเมื่อถูกเรียกจากนอกจุดเข้าของแอปพลิเคชันโดยเฉพาะฉันสังเกตเห็นเธรดลูกไม่สามารถระบุสิ่งนี้ได้หากไม่มีความสัมพันธ์กับโมดูล ' หลัก ' ฉันใส่การนำเข้าไว้ในฟังก์ชันอย่างชัดเจนเพื่อสาธิตการนำเข้าจากเธรดย่อยโดยย้ายไปที่ระดับบนสุดของ app.py จะแก้ไขได้

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

การรันโปรแกรมนี้ก่อให้เกิดข้อผิดพลาดแอตทริบิวต์:

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

... ด้วยเหตุนี้โซลูชันที่ใช้เธรด

สถานที่ตั้งอิสระ

ใช้โครงสร้างแอปพลิเคชันเหมือนเดิม แต่แก้ไข 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))

การสรุปสิ่งนี้: อันดับแรกเราต้องหา ID เธรดของเธรดหลักให้ถูกต้อง ใน Python3.4 + ไลบรารีเธรดมีthreading.main_thread()ทุกคนไม่ได้ใช้ 3.4+ ดังนั้นเราจึงค้นหาเธรดทั้งหมดโดยค้นหาเธรดหลักบันทึกเป็น ID หากเธรดหลักได้ออกไปแล้วจะไม่ปรากฏในไฟล์threading.enumerate(). เราหยิบยกRuntimeError()ในกรณีนี้จนกว่าฉันจะพบทางออกที่ดีกว่า

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

ต่อไปเราจะพบสแต็กเฟรมแรกสุดของเธรดหลัก การใช้ฟังก์ชันเฉพาะ cPython sys._current_frames()เราจะได้รับพจนานุกรมของสแต็กเฟรมปัจจุบันของเธรดทั้งหมด จากนั้นinspect.getouterframes()เราสามารถดึงสแต็กทั้งหมดสำหรับเธรดหลักและเฟรมแรกได้ current_main_frame = sys._current_frames () [main_id] base_frame = detect.getouterframes (current_main_frame) [- 1] สุดท้ายความแตกต่างระหว่างการใช้งาน Windows และ Linux ที่inspect.getouterframes()จำเป็นต้องได้รับการจัดการ ใช้ชื่อไฟล์ที่ล้างข้อมูลos.path.abspath()และos.path.dirname()ล้างสิ่งต่างๆ

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

จนถึงตอนนี้ฉันได้ทดสอบสิ่งนี้กับ Python2.7 และ 3.6 บน Windows รวมถึง Python3.4 บน WSL


0

หากคุณกำลังทำงานกับโครงการอนาคอนดาคุณสามารถสืบค้น PROJECT_ROOT จากตัวแปรสภาพแวดล้อม -> os.getenv ('PROJECT_ROOT') สิ่งนี้ใช้ได้เฉพาะเมื่อสคริปต์ถูกเรียกใช้ผ่านการรันโครงการอนาคอนดา

หากคุณไม่ต้องการให้สคริปต์ของคุณทำงานโดย anaconda-project คุณสามารถสอบถามพา ธ สัมบูรณ์ของไบนารีที่เรียกใช้งานได้ของตัวแปล Python ที่คุณใช้และแยกสตริงพา ธ ไปยังไดเร็กทอรี envs exclusiv ตัวอย่างเช่น: ตัวแปล python ของ conda env ของฉันอยู่ที่:

/ home / ผู้ใช้ / project_root / Envs / default / bin / หลาม

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

ใช้งานได้เฉพาะกับ conda-project ที่มีโครงสร้างโครงการคงที่ของ anaconda-project


0

ฉันใช้วิธี ../ เพื่อดึงเส้นทางโครงการปัจจุบัน

ตัวอย่าง: Project1 - D: \ projects

src

ConfigurationFiles

Configuration.cfg

เส้นทาง = "../ src / ConfigurationFiles / Configuration.cfg"


0

ในขณะที่เขียนไม่มีวิธีแก้ปัญหาอื่นใดที่มีอยู่ในตัวเอง ขึ้นอยู่กับตัวแปรสภาพแวดล้อมหรือตำแหน่งของโมดูลในโครงสร้างแพ็คเกจ คำตอบอันดับต้น ๆ ของโซลูชัน 'Django' ตกเป็นเหยื่อของสิ่งหลังโดยต้องการการนำเข้าแบบสัมพัทธ์ นอกจากนี้ยังมีข้อเสียที่ต้องแก้ไขโมดูลที่ระดับบนสุด

นี่ควรเป็นแนวทางที่ถูกต้องในการค้นหาเส้นทางไดเร็กทอรีของแพ็คเกจระดับบนสุด:

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

ทำงานโดยใช้ส่วนประกอบแรกในสตริงจุดที่มีอยู่__name__และใช้เป็นคีย์sys.modulesที่ส่งคืนอ็อบเจ็กต์โมดูลของแพ็กเกจระดับบนสุด ใช้__file__แอตทริบิวต์ประกอบด้วยเส้นทางที่เราต้องการหลังจากที่ตัดออกโดยใช้/__init__.pyos.path.dirname()

โซลูชันนี้มีอยู่ในตัว ทำงานได้ทุกที่ในโมดูลใด ๆ ของแพ็คเกจรวมทั้งใน__init__.pyไฟล์ระดับบนสุด


คุณสามารถเพิ่มคำอธิบายสั้น ๆ เกี่ยวกับโซลูชันของคุณและวิธีที่พวกเขาสามารถใช้เป็นโซลูชันได้หรือไม่?
LuRsT

0

ฉันต้องใช้โซลูชันที่กำหนดเองเพราะมันไม่ง่ายอย่างที่คุณคิด วิธีแก้ปัญหาของฉันขึ้นอยู่กับการตรวจสอบการติดตามสแต็ก ( inspect.stack()) + sys.pathและทำงานได้ดีไม่ว่าตำแหน่งของโมดูลหลามจะเรียกใช้ฟังก์ชันหรือล่าม (ฉันพยายามโดยเรียกใช้ใน PyCharm ในเปลือกบทกวีและอื่น ๆ ... ) นี่คือการใช้งานเต็มรูปแบบพร้อมความคิดเห็น:

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

มีคำตอบมากมายที่นี่ แต่ฉันไม่พบสิ่งง่ายๆที่ครอบคลุมทุกกรณีดังนั้นขออนุญาตแนะนำวิธีแก้ปัญหาด้วย:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.