Wie kann man das aktuelle Skriptverzeichnis richtig bestimmen?

260

Ich würde gerne sehen, wie man das aktuelle Skriptverzeichnis in Python am besten ermittelt.

Ich habe festgestellt, dass es aufgrund der vielen Möglichkeiten, Python-Code aufzurufen, schwierig ist, eine gute Lösung zu finden.

Hier sind einige Probleme:

  • __file__ist nicht definiert, wenn das Skript ausgeführt wird mit exec,execfile
  • __module__ wird nur in Modulen definiert

Anwendungsfälle:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (von einem anderen Skript, das sich in einem anderen Verzeichnis befinden kann und das ein anderes aktuelles Verzeichnis haben kann.

Ich weiß, dass es keine perfekte Lösung gibt, aber ich suche nach dem besten Ansatz, der die meisten Fälle löst.

Der am häufigsten verwendete Ansatz ist, os.path.dirname(os.path.abspath(__file__))aber dies funktioniert wirklich nicht, wenn Sie das Skript von einem anderen mit ausführen exec().

Warnung

Jede Lösung, die das aktuelle Verzeichnis verwendet, schlägt fehl. Dies kann je nach Aufruf des Skripts unterschiedlich sein oder innerhalb des laufenden Skripts geändert werden.

Bogdan
quelle
1
Können Sie genauer angeben, woher die Datei stammt? - in dem Code, der die Datei importiert (Include-fähiger Host) oder in der Datei, die importiert wird? (selbstbewusster Sklave)
Synthesizerpatel
3
Sehen Sie Ron Kalians pathlibLösung, wenn Sie Python 3.4 oder höher verwenden: stackoverflow.com/a/48931294/1011724
Dan
Die Lösung besteht also NICHT darin, ein aktuelles Verzeichnis im Code zu verwenden, sondern eine Konfigurationsdatei zu verwenden?
ZhaoGang
Interessante Entdeckung, die ich gerade gemacht habe: Wenn Sie python myfile.pyvon einer Shell aus arbeiten, funktioniert es, aber beide :!python %und :!python myfile.pyinnerhalb von vim schlagen fehl mit Das System kann den angegebenen Pfad nicht finden. Das ist ziemlich nervig. Kann jemand den Grund dafür und mögliche Problemumgehungen kommentieren?
InVader

Antworten:

231
os.path.dirname(os.path.abspath(__file__))

ist in der Tat das Beste, was Sie bekommen werden.

Es ist ungewöhnlich, ein Skript mit exec/ auszuführen execfile. Normalerweise sollten Sie die Modulinfrastruktur zum Laden von Skripten verwenden. Wenn Sie diese Methoden verwenden müssen, empfehle ich die Einstellung __file__in der globalsÜbergabe an das Skript, damit es diesen Dateinamen lesen kann.

Es gibt keine andere Möglichkeit, den Dateinamen im ausgeführten Code abzurufen: Wie Sie bemerken, befindet sich das CWD möglicherweise an einer völlig anderen Stelle.

Bobince
quelle
2
Sag niemals nie? Demnach: stackoverflow.com/a/18489147 Antwort Eine plattformübergreifende Lösung ist abspath (getsourcefile (lambda: 0))? Oder fehlt mir noch etwas?
Jeff Ellen
131

Wenn Sie wirklich den Fall abdecken möchten, über den ein Skript aufgerufen wird execfile(...), können Sie mithilfe des inspectModuls den Dateinamen (einschließlich des Pfads) ableiten. Soweit mir bekannt ist, funktioniert dies in allen von Ihnen aufgeführten Fällen:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
Sven Marnach
quelle
4
Ich denke, dies ist in der Tat die robusteste Methode, aber ich bezweifle die erklärte Notwendigkeit des OP. Ich sehe oft, dass Entwickler dies tun, wenn sie Datendateien an Orten relativ zum ausführenden Modul verwenden, aber IMO-Datendateien sollten an einem bekannten Ort abgelegt werden.
Ryan Ginstrom
14
@ Ryan LOL, wäre großartig, wenn Sie einen "bekannten Ort" definieren könnten, der plattformübergreifend ist und auch mit dem Modul geliefert wird. Ich bin bereit zu wetten, dass der einzige sichere Ort der Skriptort ist. Beachten Sie, dass das Skript nicht an diesen Speicherort schreiben sollte, aber zum Lesen von Daten ist es sicher.
Sorin
1
Trotzdem ist die Lösung nicht gut. Versuchen Sie einfach, chdir()vor der Funktion aufzurufen . Dadurch wird das Ergebnis geändert. Auch das Aufrufen des Python-Skripts aus einem anderen Verzeichnis ändert das Ergebnis, sodass es keine gute Lösung ist.
Sorin
2
os.path.expanduser("~")ist eine plattformübergreifende Methode, um das Benutzerverzeichnis abzurufen. Leider ist dies nicht die bewährte Methode von Windows, um Anwendungsdaten zu speichern.
Ryan Ginstrom
6
@sorin: Ich habe es versucht, chdir()bevor ich das Skript ausgeführt habe. es erzeugt ein korrektes Ergebnis. Ich habe versucht, das Skript aus einem anderen Verzeichnis aufzurufen, und es funktioniert auch. Die Ergebnisse sind die gleichen wie bei einer inspect.getabsfile()Lösung auf Basis .
JFS
43
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

Es funktioniert auf CPython, Jython, Pypy. Es funktioniert, wenn das Skript mit ausgeführt wird execfile()( sys.argv[0]und __file__-basierte Lösungen hier fehlschlagen würden). Es funktioniert, wenn sich das Skript in einer ausführbaren Zip-Datei (/ einem Ei) befindet . Es funktioniert, wenn das Skript PYTHONPATH=/path/to/library.zip python -mscript_to_runaus einer Zip-Datei "importiert" ( ) wird. In diesem Fall wird der Archivpfad zurückgegeben. Es funktioniert, wenn das Skript in eine eigenständige ausführbare Datei ( sys.frozen) kompiliert wird . Es funktioniert für Symlinks ( realpatheliminiert symbolische Links). Es funktioniert in einem interaktiven Interpreter. In diesem Fall wird das aktuelle Arbeitsverzeichnis zurückgegeben.

jfs
quelle
Funktioniert perfekt mit PyInstaller.
gaborous
1
Gibt es einen Grund, warum getabsfile(..)in der Dokumentation fürinspect nicht erwähnt wird ? Es wird in der Quelle angezeigt, die von dieser Seite verlinkt ist.
Evgeni Sergeev
@ EvgeniSergeev es könnte ein Fehler sein. Es ist ein einfacher Wrapper getsourcefile(), getfile()der dokumentiert wird.
JFS
24

In Python 3.4+ können Sie das einfachere pathlibModul verwenden:

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
Eugene Yarmash
quelle
2
Hervorragende Einfachheit!
Cometsong
3
Sie können wahrscheinlich verwenden Path(__file__)(keine Notwendigkeit für das inspectModul).
Peque
@Peque erzeugt dabei einen Pfad, der den Namen der aktuellen Datei enthält, nicht das übergeordnete Verzeichnis. Wenn ich versuche, das aktuelle Skriptverzeichnis abzurufen, um auf eine Datei im selben Verzeichnis zu verweisen, z. B. zu erwarten, dass eine Konfigurationsdatei im selben Verzeichnis wie das Skript geladen wird, Path(__file__)gibt dies an, /path/to/script/currentscript.pywann das OP es erhalten wollte/path/to/script/
Davos
8
Oh, ich habe es falsch verstanden, du willst das Inspektionsmodul meiden und einfach so etwas wie parent = Path(__file__).resolve().parent Das ist viel schöner.
Davos
3
@Dut A. Sie sollten .joinpath()(oder den /Operator) dafür verwenden, nicht +.
Eugene Yarmash
13

Der os.path...Ansatz war das "erledigte Ding" in Python 2.

In Python 3 finden Sie das Skriptverzeichnis wie folgt:

from pathlib import Path
cwd = Path(__file__).parents[0]
Ron Kalian
quelle
11
Oder einfach nur Path(__file__).parent. Aber cwdist eine Fehlbezeichnung, das ist nicht das aktuelle Arbeitsverzeichnis , sondern das Verzeichnis der Datei . Sie könnten gleich sein, aber das ist normalerweise nicht der Fall.
Nuno André
5

Verwenden Sie einfach os.path.dirname(os.path.abspath(__file__))und prüfen Sie sehr sorgfältig, ob für den Fall, in dem execes verwendet wird, ein wirklicher Bedarf besteht. Es könnte ein Zeichen für ein problematisches Design sein, wenn Sie Ihr Skript nicht als Modul verwenden können.

Denken Sie an Zen of Python # 8 , und wenn Sie der Meinung sind, dass es ein gutes Argument für einen Anwendungsfall gibt, für den es funktionieren muss exec, teilen Sie uns bitte einige Details zum Hintergrund des Problems mit.

wim
quelle
2
Wenn Sie nicht mit exec () ausgeführt werden, verlieren Sie den Debugger-Kontext. Auch exec () soll erheblich schneller sein als das Starten eines neuen Prozesses.
Sorin
@sorin Es geht nicht darum, einen neuen Prozess zu starten, also ist das ein Strohmann-Argument. Es ist eine Frage von exec vs mit einem Import- oder Funktionsaufruf.
wim
4

Würde

import os
cwd = os.getcwd()

Tun Sie, was Sie wollen? Ich bin mir nicht sicher, was genau Sie mit dem "aktuellen Skriptverzeichnis" meinen. Was wäre die erwartete Ausgabe für die von Ihnen angegebenen Anwendungsfälle?

Will McCutchen
quelle
3
Es würde nicht helfen. Ich glaube, @bogdan sucht nach dem Verzeichnis für das Skript, das sich oben im Aufrufstapel befindet. dh in all seinen Fällen sollte das Verzeichnis gedruckt werden, in dem sich 'myfile.py' befindet. Ihre Methode druckt jedoch nur das Verzeichnis der aufrufenden Datei exec('myfile.py'), genau wie __file__und sys.argv[0].
Zhang18
Ja, das macht Sinn. Ich wollte nur sicherstellen, dass @bogdan etwas Einfaches nicht übersieht, und ich konnte nicht genau sagen, was sie wollten.
Will McCutchen
3

Erstens ... ein paar fehlende Anwendungsfälle hier, wenn wir über Möglichkeiten sprechen, anonymen Code einzufügen.

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

Die eigentliche Frage ist jedoch, was ist Ihr Ziel - versuchen Sie, irgendeine Art von Sicherheit durchzusetzen? Oder sind Sie nur daran interessiert, was geladen wird?

Wenn Sie an Sicherheit interessiert sind , spielt der Dateiname, der über exec / execfile importiert wird, keine Rolle. Sie sollten rexec verwenden , das Folgendes bietet:

Dieses Modul enthält die RExec-Klasse, die die Methoden r_eval (), r_execfile (), r_exec () und r_import () unterstützt. Hierbei handelt es sich um eingeschränkte Versionen der Standard-Python-Funktionen eval (), execfile () sowie der Anweisungen exec und import. Code, der in dieser eingeschränkten Umgebung ausgeführt wird, hat nur Zugriff auf Module und Funktionen, die als sicher gelten. Sie können RExec-Funktionen nach Bedarf hinzufügen oder entfernen.

Wenn dies jedoch eher eine akademische Angelegenheit ist, finden Sie hier einige doofe Ansätze, in die Sie möglicherweise etwas tiefer eintauchen können.

Beispielskripte:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

Ausgabe

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

Dies ist natürlich eine ressourcenintensive Methode. Sie würden Ihren gesamten Code nachverfolgen. Nicht sehr effizient. Aber ich denke, es ist ein neuartiger Ansatz, da er auch dann weiter funktioniert, wenn Sie tiefer in das Nest eindringen. Sie können 'eval' nicht überschreiben. Obwohl Sie können execfile außer Kraft setzen ().

Beachten Sie, dass dieser Ansatz nur exec / execfile abdeckt, nicht 'import'. Für das Laden von Modulen auf höherer Ebene können Sie möglicherweise sys.path_hooks verwenden (mit freundlicher Genehmigung von PyMOTW).

Das ist alles, was ich auf dem Kopf habe.

Synthesizerpatel
quelle
2

Hier ist eine Teillösung, die immer noch besser ist als alle bisher veröffentlichten.

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

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

Jetzt funktioniert dies bei allen Aufrufen, aber wenn jemand chdir()das aktuelle Verzeichnis ändert, schlägt dies ebenfalls fehl.

Anmerkungen:

  • sys.argv[0]wird nicht funktionieren, wird zurückkehren, -cwenn Sie das Skript mit ausführenpython -c "execfile('path-tester.py')"
  • Ich habe einen vollständigen Test unter https://gist.github.com/1385555 veröffentlicht, und Sie können ihn gerne verbessern.
Sorin
quelle
1

Dies sollte in den meisten Fällen funktionieren:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
Jahid
quelle
5
Diese Lösung verwendet das aktuelle Verzeichnis und wird in der Frage ausdrücklich angegeben, dass eine solche Lösung fehlschlägt.
Skyking
1

Hoffentlich hilft dies: - Wenn Sie ein Skript / Modul von einem beliebigen Ort aus ausführen, können Sie auf die __file__Variable zugreifen , bei der es sich um eine Modulvariable handelt, die den Speicherort des Skripts darstellt.

Wenn Sie dagegen den Interpreter verwenden, haben Sie keinen Zugriff auf diese Variable. Dort erhalten Sie einen Namen NameErrorund erhalten os.getcwd()das falsche Verzeichnis, wenn Sie die Datei von einem anderen Ort aus ausführen.

Diese Lösung sollte Ihnen in allen Fällen das bieten, wonach Sie suchen:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

Ich habe es nicht gründlich getestet, aber es hat mein Problem gelöst.

Kennzeichen
quelle
Dies gibt Datei, nicht Verzeichnis
Shital Shah