เมื่อไม่นานมานี้ฉันพยายามทำสิ่งที่คล้ายกันและพบว่าคำตอบเหล่านี้ไม่เพียงพอสำหรับกรณีการใช้งานของฉัน (ไลบรารีแบบกระจายที่ต้องตรวจหารูทของโปรเจ็กต์) ส่วนใหญ่ฉันต่อสู้กับสภาพแวดล้อมและแพลตฟอร์มที่แตกต่างกันและยังไม่พบสิ่งที่เป็นสากลอย่างสมบูรณ์แบบ
รหัสท้องถิ่นให้กับโครงการ
ฉันเคยเห็นตัวอย่างนี้พูดถึงและใช้ในสถานที่บางแห่ง 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
<ROOT>/__init__.pyอยู่จริง?