Как надежно открыть файл в том же каталоге, что и скрипт Python

157

Я использовал для открытия файлов, которые были в том же каталоге, что и текущий скрипт Python, просто используя команду

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

Однако я обнаружил, что когда скрипт запускается в Windows, дважды щелкнув по нему, он попытается открыть файл из неправильного каталога.

С тех пор я использовал команду вида

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

всякий раз, когда я хотел открыть файл. Это работает для моего конкретного использования, но я не уверен, что sys.path[0]может произойти сбой в другом случае использования.

Итак, мой вопрос: каков наилучший и самый надежный способ открыть файл, который находится в той же директории, что и текущий запущенный скрипт Python?

Вот что мне удалось выяснить до сих пор:

  • os.getcwd()и os.path.abspath('')вернуть «текущий рабочий каталог», а не каталог скрипта.

  • os.path.dirname(sys.argv[0])и os.path.dirname(__file__)вернуть путь, используемый для вызова скрипта, который может быть относительным или даже пустым (если скрипт находится в cwd). Кроме того, __file__не существует, когда скрипт выполняется в IDLE или PythonWin.

  • sys.path[0]и, os.path.abspath(os.path.dirname(sys.argv[0]))кажется, вернуть каталог скриптов. Я не уверен, есть ли разница между этими двумя.

Редактировать:

Я просто понял, что то, что я хочу сделать, лучше описать как «открыть файл в том же каталоге, что и содержащий модуль». Другими словами, если я импортирую написанный мной модуль, который находится в другом каталоге, и этот модуль открывает файл, я хочу, чтобы он искал файл в каталоге модуля. Я не думаю, что что-то, что я нашел, может это сделать ...

dln385
источник

Ответы:

199

Я всегда использую:

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

join()Вызов предваряется текущего рабочего каталога, но в документации сказано , что если какой - то путь является абсолютным, все другие пути слева от него отбрасываются. Поэтому getcwd()отбрасывается, когда dirname(__file__)возвращает абсолютный путь.

Кроме того, realpathвызов разрешает символические ссылки, если таковые найдены. Это позволяет избежать проблем при развертывании с помощью setuptools в системах Linux (сценарии имеют ссылки по /usr/bin/крайней мере на Debian).

Вы можете использовать следующее для открытия файлов в той же папке:

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

Я использую это для объединения ресурсов с несколькими приложениями Django как в Windows, так и в Linux, и это работает как шарм!

Андре Карон
источник
4
Если __file__нельзя использовать, используйте sys.argv[0]вместо dirname(__file__). Остальное должно работать как положено. Мне нравится использовать, __file__потому что в коде библиотеки, sys.argv[0]может вообще не указывать на ваш код, особенно если он импортирован через какой-то сторонний скрипт.
Андре Карон
1
Проблема в том, что он будет отличаться, если вы запускаете файл непосредственно из прерывателя или если он импортирован. Смотрите мой ответ для различий между файлом и sys.argv [0]
Zimm3r
Так правильно ли говорить, что вариация, описанная в ответе Zimm3r, адресована с использованием realpath( join( getcwd(), dirname(__file__) ))описанного здесь?
pianoJames
44

Цитировать из документации Python:

Как инициализируется при запуске программы, первый элемент этого списка, path [0], является каталогом, содержащим скрипт, который использовался для вызова интерпретатора Python. Если каталог скриптов недоступен (например, если интерпретатор вызывается в интерактивном режиме или если скрипт читается из стандартного ввода), path [0] является пустой строкой, которая направляет Python для поиска модулей в текущем каталоге в первую очередь. Обратите внимание, что каталог скриптов вставляется перед записями, вставленными в результате PYTHONPATH.

sys.path [0] - это то, что вы ищете.

Красная обезьяна
источник
10
И для полного пути файла: os.path.join(sys.path[0], 'some file.txt'). Это должно правильно обрабатывать пробелы и косые черты во всех системах.
Джектоз
Это ответ на первый вопрос, а не тот, что после EDIT.
mcoolive
22

Хорошо, вот что я делаю

sys.argv - это всегда то, что вы вводите в терминал или используете в качестве пути к файлу при выполнении его с помощью python.exe или pythonw.exe

Например, вы можете запустить файл text.py несколькими способами, каждый из которых даст вам свой ответ, он всегда даст вам путь, по которому был набран 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

Итак, знайте, что вы можете получить имя файла, большое дело, теперь, чтобы получить каталог приложения, который вы знаете, используйте os.path, в частности, abspath и dirname

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

Это выведет это:

   C:\Documents and Settings\Admin\

это всегда будет выводиться независимо от того, введете ли вы python test.py или python «C: \ Documents and Settings \ Admin \ test.py»

Проблема с использованием __file__ Рассмотрим эти два файла 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]

Вывод "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

Вывод "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

Итак, как вы видите, файл всегда дает вам файл python, из которого он запускается, а sys.argv [0] дает вам файл, который вы всегда запускали из интерпретатора. В зависимости от ваших потребностей вам нужно будет выбрать тот, который лучше всего соответствует вашим потребностям.

Zimm3r
источник
3
Это сложное доказательство того, что реализация отражает документацию. __file__как предполагается , чтобы «всегда даст вам путь к текущему файлу», и sys.argv[0]как предполагается , чтобы «всегда дают путь к сценарию , который инициировал процесс». В любом случае, использование __file__в вызываемом скрипте всегда дает точные результаты.
Андре Карон
Если у вас есть ссылка __file__на верхний уровень скрипта, он будет работать как положено.
Мэтью Шинкель
-1

Я смог успешно использовать код, предоставленный dcolish, так как у меня была похожая проблема с чтением определенного текстового файла. Файл не находится в том же CWD, что и файл Python.

Жаклин Хортон
источник
1
Пожалуйста, не добавляйте «спасибо» в качестве ответа. Как только вы наберете достаточную репутацию , вы сможете голосовать за вопросы и ответы , которые показались вам полезными. - Из обзора
Роберто Кабони
-3

Я бы сделал это так:

from os.path import abspath, exists

f_path = abspath("fooabar.txt")

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

Приведенный выше код создает абсолютный путь к файлу с использованием abspath и эквивалентен использованию normpath(join(os.getcwd(), path))[это из pydocs]. Затем он проверяет, существует ли этот файл на самом деле, а затем использует диспетчер контекста, чтобы открыть его, чтобы вам не пришлось вспоминать о вызове close для дескриптора файла. ИМХО, делая это таким образом, вы избавите вас от боли в долгосрочной перспективе.

dcolish
источник
Это не отвечает на вопрос автора. dln385 специально сказал, что os.path.abspathне разрешает пути к файлам в той же папке, что и скрипт, если скрипт не находится в текущем каталоге.
Андре Карон
AH! Я предположил, что пользователь запускал этот сценарий в том же каталоге, что и файл, который он хотел прочитать, а НЕ в модуле dir чего-то в их PYTHONPATH. Это научит меня делать предположения ...
dcolish 30.10.10
abspath не будет работать, так как среда выполнения Python не может выполнять поиск в файловой системе ОС, используя такую ​​функцию.
Акшат Такар