Jak niezawodnie otworzyć plik w tym samym katalogu, co skrypt w Pythonie

157

Kiedyś otwierałem pliki, które znajdowały się w tym samym katalogu, co aktualnie uruchomiony skrypt Pythona, po prostu używając polecenia takiego jak

open("Some file.txt", "r")

Jednak odkryłem, że gdy skrypt był uruchamiany w systemie Windows przez dwukrotne kliknięcie, próbował otworzyć plik z niewłaściwego katalogu.

Od tego czasu używam polecenia formularza

open(os.path.join(sys.path[0], "Some file.txt"), "r")

kiedykolwiek chciałem otworzyć plik. Działa to dla mojego konkretnego zastosowania, ale nie jestem pewien, czy sys.path[0]może się nie powieść w innym przypadku użycia.

Więc moje pytanie brzmi: jaki jest najlepszy i najbardziej niezawodny sposób na otwarcie pliku, który znajduje się w tym samym katalogu, co aktualnie uruchomiony skrypt Pythona?

Oto, co udało mi się do tej pory ustalić:

  • os.getcwd()i os.path.abspath('')zwraca „bieżący katalog roboczy”, a nie katalog skryptów.

  • os.path.dirname(sys.argv[0])i os.path.dirname(__file__)zwróć ścieżkę używaną do wywołania skryptu, która może być względna lub nawet pusta (jeśli skrypt znajduje się w cwd). Ponadto __file__nie istnieje, gdy skrypt jest uruchamiany w środowisku IDLE lub PythonWin.

  • sys.path[0]i os.path.abspath(os.path.dirname(sys.argv[0]))wydaje się zwracać katalog skryptów. Nie jestem pewien, czy jest jakaś różnica między tymi dwoma.

Edytować:

Właśnie zdałem sobie sprawę, że to, co chcę zrobić, lepiej byłoby opisać jako „otwórz plik w tym samym katalogu, co zawierający moduł”. Innymi słowy, jeśli importuję moduł, który napisałem, który znajduje się w innym katalogu i ten moduł otwiera plik, chcę, aby szukał pliku w katalogu modułu. Nie sądzę, aby cokolwiek, co znalazłem, było w stanie to zrobić ...

dln385
źródło

Odpowiedzi:

199

Zawsze używam:

__location__ = os.path.realpath(
    os.path.join(os.getcwd(), os.path.dirname(__file__)))

join()Wezwanie poprzedza aktualny katalog roboczy, ale dokumentacja mówi, że jeśli ktoś jest ścieżka bezwzględna, wszystkie inne ścieżki lewej strony są odrzucane. Dlatego getcwd()jest usuwany, gdy dirname(__file__)zwraca ścieżkę bezwzględną.

Ponadto realpathwywołanie rozwiązuje dowiązania symboliczne, jeśli takie zostaną znalezione. Pozwala to uniknąć problemów podczas wdrażania z setuptools w systemach Linux (skrypty są dowiązane symbolicznie /usr/bin/- przynajmniej w Debianie).

Aby otworzyć pliki w tym samym folderze, możesz skorzystać z:

f = open(os.path.join(__location__, 'bundled-resource.jpg'));
# ...

Używam tego do łączenia zasobów z kilkoma aplikacjami Django zarówno w systemie Windows, jak i Linux i działa to jak urok!

André Caron
źródło
4
Jeśli __file__nie można użyć, użyj sys.argv[0]zamiast dirname(__file__). Reszta powinna działać zgodnie z oczekiwaniami. Lubię używać, __file__ponieważ w kodzie biblioteki sys.argv[0]może w ogóle nie wskazywać na twój kod, szczególnie jeśli jest importowany za pomocą skryptu innej firmy.
André Caron
1
Problem z tym polega na tym, że będzie się różnić, jeśli uruchamiany plik pochodzi bezpośrednio z przerywacza, czy jest importowany. Zobacz moją odpowiedź dotyczącą różnic między plikiem a sys.argv [0]
Zimm3r
Czy zatem słuszne jest stwierdzenie, że odmiana opisana w odpowiedzi Zimm3r jest rozwiązana przy użyciu realpath( join( getcwd(), dirname(__file__) ))opisanego tutaj?
pianoJames
44

Cytat z dokumentacji Pythona:

Zgodnie z inicjalizacją podczas uruchamiania programu, pierwsza pozycja na tej liście, ścieżka [0], jest katalogiem zawierającym skrypt, który został użyty do wywołania interpretera Pythona. Jeśli katalog skryptu nie jest dostępny (np. Jeśli interpreter jest wywoływany interaktywnie lub jeśli skrypt jest czytany ze standardowego wejścia), ścieżka [0] jest pustym ciągiem, który kieruje Pythona do wyszukiwania modułów w bieżącym katalogu w pierwszej kolejności. Zauważ, że katalog skryptów jest wstawiany przed wpisami wstawionymi w wyniku PYTHONPATH.

sys.path [0] jest tym, czego szukasz.

CZERWONA MAŁPA
źródło
10
A dla pełnej ścieżki do pliku: os.path.join(sys.path[0], 'some file.txt'). Powinno to poprawnie obsługiwać spacje i ukośniki we wszystkich systemach.
Jacktose,
To odpowiedź na pierwsze pytanie, a nie to po EDYCJI.
mcoolive
22

Ok, oto co robię

sys.argv jest zawsze tym, co wpisujesz w terminalu lub używasz jako ścieżki do pliku podczas wykonywania go za pomocą python.exe lub pythonw.exe

Na przykład możesz uruchomić plik text.py na kilka sposobów, każdy z nich daje inną odpowiedź, zawsze podają ścieżkę, w której wpisano python.

    C:\Documents and Settings\Admin>python test.py
    sys.argv[0]: test.py
    C:\Documents and Settings\Admin>python "C:\Documents and Settings\Admin\test.py"
    sys.argv[0]: C:\Documents and Settings\Admin\test.py

Ok, więc wiedz, że możesz uzyskać nazwę pliku, wielka sprawa, teraz, aby uzyskać katalog aplikacji, który możesz znać, użyj os.path, w szczególności abspath i dirname

    import sys, os
    print os.path.dirname(os.path.abspath(sys.argv[0]))

To da to:

   C:\Documents and Settings\Admin\

zawsze wyświetli to bez względu na to, czy wpiszesz python test.py czy python "C: \ Documents and Settings \ Admin \ test.py"

Problem z używaniem __file__ Rozważ te dwa pliki test.py

import sys
import os

def paths():
        print "__file__: %s" % __file__
        print "sys.argv: %s" % sys.argv[0]

        a_f = os.path.abspath(__file__)
        a_s = os.path.abspath(sys.argv[0])

        print "abs __file__: %s" % a_f
        print "abs sys.argv: %s" % a_s

if __name__ == "__main__":
    paths()

import_test.py

import test
import sys

test.paths()

print "--------"
print __file__
print sys.argv[0]

Wyjście „python test.py”

C:\Documents and Settings\Admin>python test.py
__file__: test.py
sys.argv: test.py
abs __file__: C:\Documents and Settings\Admin\test.py
abs sys.argv: C:\Documents and Settings\Admin\test.py

Wynik „python test_import.py”

C:\Documents and Settings\Admin>python test_import.py
__file__: C:\Documents and Settings\Admin\test.pyc
sys.argv: test_import.py
abs __file__: C:\Documents and Settings\Admin\test.pyc
abs sys.argv: C:\Documents and Settings\Admin\test_import.py
--------
test_import.py
test_import.py

Jak widzisz, plik zawsze podaje plik Pythona, z którego jest uruchamiany, gdzie jako sys.argv [0] zawsze podaje plik, który uruchomiłeś z interpretera. W zależności od potrzeb będziesz musiał wybrać, który z nich najlepiej odpowiada Twoim potrzebom.

Zimm3r
źródło
3
To rozbudowany dowód na to, że implementacja odzwierciedla dokumentację. __file__jest niby do „zawsze daje ścieżkę do bieżącego pliku” i sys.argv[0]jest niby do „zawsze podać ścieżkę do skryptu, który zainicjował proces”. W każdym razie użycie __file__w wywołanym skrypcie zawsze daje dokładne wyniki.
André Caron
Jeśli masz odwołanie do __file__na najwyższym poziomie skryptu, będzie działać zgodnie z oczekiwaniami.
Matthew Schinckel
-1

Udało mi się pomyślnie użyć kodu dostarczonego przez dcolish, ponieważ miałem podobny problem z odczytem określonego pliku tekstowego. Plik nie znajduje się w tym samym cwd co plik Pythona.

Jaclyn Horton
źródło
1
Nie dodawaj „dziękuję” jako odpowiedzi. Gdy zdobędziesz wystarczającą reputację , będziesz mógł głosować na pytania i odpowiedzi, które uznasz za pomocne. - Z recenzji
Roberto Caboni
-3

Zrobiłbym to w ten sposób:

from os.path import abspath, exists

f_path = abspath("fooabar.txt")

if exists(f_path):
    with open(f_path) as f:
        print f.read()

Powyższy kod buduje bezwzględną ścieżkę do pliku przy użyciu abspath i jest równoważny użyciu normpath(join(os.getcwd(), path))[pochodzi z pydoc]. Następnie sprawdza, czy ten plik faktycznie istnieje, a następnie używa menedżera kontekstu, aby go otworzyć, więc nie musisz pamiętać o wywołaniu close na uchwycie pliku. IMHO, robienie tego w ten sposób na dłuższą metę zaoszczędzi ci wiele bólu.

dcolish
źródło
To nie odpowiada na pytanie nadawcy. dln385 powiedział konkretnie, że os.path.abspathnie rozwiązuje ścieżek do plików w tym samym folderze co skrypt, jeśli skrypt nie znajduje się w bieżącym katalogu.
André Caron
AH! Założyłem, że użytkownik uruchamia ten skrypt w tym samym katalogu, co plik, który chciał przeczytać, a NIE w katalogu modułu czegoś w swojej PYTHONPATH. To nauczy mnie robić przypuszczenia ...
dcolish
abspath nie będzie działać, ponieważ środowisko wykonawcze Pythona nie może wyszukiwać w systemie plików OS przy użyciu takiej funkcji.
akshat thakar