Ich habe kürzlich versucht, etwas Ähnliches zu tun, und ich habe festgestellt, dass diese Antworten für meine Anwendungsfälle unzureichend sind (eine verteilte Bibliothek, die den Projektstamm erkennen muss). Hauptsächlich habe ich mit verschiedenen Umgebungen und Plattformen gekämpft und immer noch nichts vollkommen Universelles gefunden.
Lokaler Code für das Projekt
Ich habe dieses Beispiel erwähnt und an einigen Stellen verwendet, Django usw.
import os
print(os.path.dirname(os.path.abspath(__file__)))
So einfach das ist, es funktioniert nur, wenn die Datei, in der sich das Snippet befindet, tatsächlich Teil des Projekts ist. Wir rufen nicht das Projektverzeichnis ab, sondern das Verzeichnis des Snippets
In ähnlicher Weise bricht der sys.modules- Ansatz zusammen, wenn er von außerhalb des Einstiegspunkts der Anwendung aufgerufen wird. Insbesondere habe ich festgestellt, dass ein untergeordneter Thread dies nicht ohne Bezug zum Hauptmodul feststellen kann . Ich habe den Import explizit in eine Funktion eingefügt, um einen Import aus einem untergeordneten Thread zu demonstrieren. Wenn Sie ihn auf die oberste Ebene von app.py verschieben, wird dies behoben.
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
Das Ausführen dieses Programms führt zu einem Attributfehler:
>>> 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__'
... daher eine Threading-basierte Lösung
Standortunabhängig
Verwenden Sie dieselbe Anwendungsstruktur wie zuvor, ändern Sie jedoch 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))
Aufschlüsselung: Zuerst wollen wir die Thread-ID des Haupt-Threads genau finden. In Python3.4 + hat die Threading-Bibliothek threading.main_thread()jedoch nicht jeder 3.4+ verwendet, sodass wir alle Threads nach dem Haupt-Thread durchsuchen und dessen ID speichern. Wenn der Hauptthread bereits beendet wurde, wird er nicht in der Liste aufgeführt threading.enumerate(). Wir erheben RuntimeError()in diesem Fall eine, bis ich eine bessere Lösung finde.
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")
Als nächstes finden wir den allerersten Stapelrahmen des Hauptthreads. Mit der cPython-spezifischen Funktion erhalten sys._current_frames() wir ein Wörterbuch des aktuellen Stapelrahmens jedes Threads. Mit inspect.getouterframes()können wir dann den gesamten Stapel für den Haupt-Thread und den allerersten Frame abrufen. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Schließlich müssen die Unterschiede zwischen Windows- und Linux-Implementierungen von inspect.getouterframes()behandelt werden. Verwenden Sie den bereinigten Dateinamen os.path.abspath()und os.path.dirname()bereinigen Sie die Dinge.
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))
Bisher habe ich dies unter Python2.7 und 3.6 unter Windows sowie unter Python3.4 unter WSL getestet
<ROOT>/__init__.pyexistieren?