Как правильно определить текущий каталог скриптов?

Я хотел бы посмотреть, как лучше всего определить текущий каталог скриптов в Python.

Я обнаружил, что из-за множества способов вызова кода Python трудно найти хорошее решение.

Вот некоторые проблемы:

  • __file__ не определен, если сценарий выполняется с exec, execfile
  • __module__ определяется только в модулях

Случаи применения:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (из другого скрипта, который может быть расположен в другом каталоге и может иметь другой текущий каталог.

Я знаю, что идеального решения не существует, но я ищу лучший подход, который решает большинство случаев.

Чаще всего используется подход os.path.dirname(os.path.abspath(__file__)), но он действительно не работает, если вы выполняете сценарий из другого с помощью exec().

Предупреждение

Любое решение, использующее текущий каталог, завершится ошибкой, это может отличаться в зависимости от способа вызова сценария или его можно изменить внутри запущенного сценария.


person bogdan    schedule 15.09.2010    source источник
comment
Не могли бы вы уточнить, откуда вам нужно знать, откуда взялся файл? - в коде, импортирующем файл (хост с поддержкой включения) или в импортированном файле? (осознающий себя раб)   -  person synthesizerpatel    schedule 02.06.2011
comment
См. pathlib решение Рона Калиана, если вы используете python 3.4 или выше: stackoverflow.com/a/48931294/1011724   -  person Dan    schedule 22.08.2018
comment
Таким образом, решение состоит в том, чтобы НЕ использовать какой-либо текущий каталог в коде, а использовать какой-то файл конфигурации?   -  person ZhaoGang    schedule 13.09.2018
comment
Я только что сделал интересное открытие: при выполнении python myfile.py из оболочки оно работает, но и :!python %, и :!python myfile.py изнутри vim не работают с Система не может найти указанный путь. Это довольно раздражает. Может ли кто-нибудь прокомментировать причину этого и возможные обходные пути?   -  person inVader    schedule 27.11.2018


Ответы (15)


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

действительно лучшее, что вы собираетесь получить.

Выполнение сценария с _2 _ / _ 3_ необычно; обычно вы должны использовать инфраструктуру модуля для загрузки скриптов. Если вам необходимо использовать эти методы, я предлагаю установить __file__ в globals, который вы передаете сценарию, чтобы он мог прочитать это имя файла.

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

person bobince    schedule 15.09.2010
comment
Никогда не говори никогда? Согласно этому: stackoverflow.com/a/18489147 ответ, кроссплатформенное решение abspath (getsourcefile (lambda: 0))? Или мне что-то еще не хватает? - person Jeff Ellen; 12.07.2018
comment
Обновление с помощью pathlib: pathlib.Path (file) .resolve () / '..' - person dominecf; 11.06.2021

Если вы действительно хотите рассмотреть случай, когда сценарий вызывается через execfile(...), вы можете использовать модуль inspect для определения имени файла (включая путь). Насколько мне известно, это будет работать для всех перечисленных вами случаев:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
person Sven Marnach    schedule 02.06.2011
comment
Я думаю, что это действительно самый надежный метод, но я сомневаюсь, что ОП в этом заявляет. Я часто вижу, как разработчики делают это, когда используют файлы данных в местах, относящихся к исполняющему модулю, но файлы данных IMO следует размещать в известном месте. - person Ryan Ginstrom; 03.06.2011
comment
@Ryan LOL, было бы здорово, если бы вы могли определить известное местоположение, которое является мультиплатформенным и которое также поставляется с модулем. Готов поспорить, что единственное безопасное место - это местоположение сценария. Обратите внимание, что это не означает, что сценарий должен писать в это место, но для чтения данных это безопасно. - person sorin; 22.11.2011
comment
Тем не менее, решение нехорошее, просто попробуйте вызвать chdir() перед функцией, это изменит результат. Также вызов сценария python из другого каталога изменит результат, поэтому это не очень хорошее решение. - person sorin; 22.11.2011
comment
os.path.expanduser("~") - это кроссплатформенный способ получить каталог пользователя. К сожалению, это не лучшая практика Windows для того, чтобы сохранять данные приложения. - person Ryan Ginstrom; 01.12.2011
comment
@sorin: Я пробовал chdir() перед запуском скрипта; дает правильный результат. Я пробовал вызвать скрипт из другого каталога, и он тоже работает. Результаты такие же, как у решения на основе inspect.getabsfile(). - person jfs; 05.04.2014
comment
Этот метод работает даже при запуске из фреймворка на основе Python, а файл - нет. - person Jetse; 13.06.2014

#!/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())

Работает на CPython, Jython, Pypy. Он работает, если скрипт выполняется с использованием execfile() (решения на основе sys.argv[0] и __file__ здесь не сработают). Он работает, если сценарий находится внутри исполняемого zip-файла (/ яйца). Работает, если сценарий "импортирован" (PYTHONPATH=/path/to/library.zip python -mscript_to_run) из zip-файла; в этом случае он возвращает путь к архиву. Он работает, если сценарий скомпилирован в отдельный исполняемый файл (sys.frozen). Это работает для символических ссылок (realpath исключает символические ссылки). Работает в интерактивном интерпретаторе; в этом случае он возвращает текущий рабочий каталог.

person jfs    schedule 05.04.2014
comment
Прекрасно работает с PyInstaller. - person gaborous; 16.08.2015
comment
Есть ли причина, по которой getabsfile(..) не упоминается в документации для inspect? Он появляется в источнике, на который есть ссылка на этой странице. - person Evgeni Sergeev; 14.11.2015
comment
@EvgeniSergeev это может быть ошибка. Это простая задокументированная оболочка для getsourcefile(), getfile(). - person jfs; 14.11.2015

В Python 3.4+ вы можете использовать более простой модуль pathlib:

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent

Вы также можете использовать __file__, чтобы полностью отказаться от модуля inspect:

from pathlib import Path
parent = Path(__file__).resolve().parent
person Eugene Yarmash    schedule 25.05.2017
comment
В Windows (10) я получил эту ошибку: TypeError: unsupported operand type(s) for +: 'WindowsPath' and 'str', когда я попытался добавить другую строку к пути к файлу с помощью оператора +. Обходной путь, который сработал, заключался в заключении *parent* в str() функцию. - person Daniel Dut; 04.07.2018
comment
@Dut A. Вам следует использовать .joinpath() (или оператор /), а не +. - person Eugene Yarmash; 27.08.2018
comment
Здесь нет необходимости использовать .resolve () - person 5norre; 27.01.2021
comment
ПРИМЕЧАНИЕ. Если вам нужен абсолютный путь без символических ссылок, вы должны использовать resolve (). Документы здесь - person Jesuisme; 21.04.2021

Подход os.path... был «готовым делом» в Python 2.

В Python 3 вы можете найти каталог сценария следующим образом:

from pathlib import Path
script_dir = Path(__file__).parent
person Ron Kalian    schedule 22.02.2018
comment
Или просто Path(__file__).parent. Но cwd - неправильное название, это не текущий рабочий каталог, а каталог файлов. Они могут быть одинаковыми, но обычно это не так. - person Nuno André; 18.03.2019
comment
ПРИМЕЧАНИЕ. Это вернет только относительный путь. Для абсолютного пути вы должны использовать resolve (). Документы здесь - person Jesuisme; 21.04.2021

Просто используйте os.path.dirname(os.path.abspath(__file__)) и очень внимательно проверьте, есть ли реальная необходимость в случае, когда используется exec. Если вы не можете использовать свой скрипт как модуль, это может быть признаком проблемного дизайна.

Помните о Zen of Python # 8, и если вы считаете, что есть хороший аргумент в пользу варианта использования, когда он должен работать для exec, то сообщите нам более подробную информацию об истории создания проблема.

person wim    schedule 04.06.2011
comment
Если вы не работаете с exec (), вы потеряете контекст отладчика. Также предполагается, что exec () будет значительно быстрее, чем запуск нового процесса. - person sorin; 22.11.2011
comment
@sorin Это не вопрос между exec и запуском нового процесса, так что это соломенный аргумент. Это вопрос exec против использования импорта или вызова функции. - person wim; 23.01.2019

Было бы

import os
cwd = os.getcwd()

делай что хочешь? Я не уверен, что именно вы подразумеваете под «текущим каталогом сценариев». Каким будет ожидаемый результат для приведенных вами вариантов использования?

person Will McCutchen    schedule 15.09.2010
comment
Это не поможет. Я считаю, что @bogdan ищет каталог для сценария, который находится наверху стека вызовов. т.е. во всех случаях он должен печатать каталог, в котором находится myfile.py. Тем не менее, ваш метод будет печатать только каталог файла, который вызывает exec('myfile.py'), так же, как __file__ и sys.argv[0]. - person Zhang18; 15.09.2010
comment
Да, в этом есть смысл. Я просто хотел убедиться, что @bogdan не упускает из виду что-то простое, и я не мог точно сказать, чего они хотели. - person Will McCutchen; 15.09.2010

Во-первых ... здесь отсутствует пара примеров использования, если мы говорим о способах внедрения анонимного кода ...

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

Но настоящий вопрос в том, какова ваша цель - пытаетесь ли вы обеспечить какую-то безопасность? Или вас просто интересует, что загружается.

Если вас интересует безопасность, имя файла, импортируемого через exec / execfile, несущественно - вам следует использовать rexec, который предлагает следующее:

Этот модуль содержит класс RExec, который поддерживает методы r_eval (), r_execfile (), r_exec () и r_import (), которые являются ограниченными версиями стандартных функций Python eval (), execfile () и операторов exec и import. Код, выполняемый в этой ограниченной среде, будет иметь доступ только к модулям и функциям, которые считаются безопасными; вы можете создать подкласс RExec, добавляя или удаляя возможности по желанию.

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

Примеры скриптов:

./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)

Вывод

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

Конечно, это ресурсоемкий способ сделать это, вы будете отслеживать весь свой код ... Не очень эффективный. Но я думаю, что это новый подход, поскольку он продолжает работать, даже когда вы углубляетесь в гнездо. Вы не можете переопределить eval. Хотя вы можете переопределить execfile ().

Обратите внимание, что этот подход охватывает только exec / execfile, но не import. Для подключения нагрузки «модуль» более высокого уровня вы можете использовать sys.path_hooks (доработка любезно предоставлена ​​PyMOTW).

Это все, что у меня в голове.

person synthesizerpatel    schedule 04.06.2011

Примечание: теперь этот ответ представляет собой пакет

https://github.com/heetbeet/locate

$ pip install locate

$ python
>>> from locate import this_dir
>>> print(this_dir())
C:/Users/simon

Для .py скриптов, а также для интерактивного использования:

Я часто использую каталог моих сценариев (для доступа к файлам, хранящимся рядом с ними), но я также часто запускаю эти сценарии в интерактивной оболочке для целей отладки. Я определяю __dirpath__ как:

  • При запуске или импорте файла .py - базовый каталог файла. Это всегда правильный путь.
  • При запуске .ipyn записной книжки - текущий рабочий каталог. Это всегда правильный путь, поскольку Jupyter устанавливает рабочий каталог как .ipynb базовый каталог.
  • При запуске в REPL текущий рабочий каталог. Хм, а каков правильный путь, когда код отсоединен от файла? Лучше возложите на себя ответственность за изменение правильного пути перед вызовом REPL.

Python 3.4 (и выше):

from pathlib import Path
__dirpath__ = Path(globals().get("__file__", "./_")).absolute().parent

Python 2 (и выше):

import os
__dirpath__ = os.path.dirname(os.path.abspath(globals().get("__file__", "./_")))

Объяснение:

  • globals() возвращает все глобальные переменные в виде словаря.
  • .get("__file__", "./_") возвращает значение из ключа "__file__", если оно существует в globals(), в противном случае возвращает предоставленное значение по умолчанию "./_".
  • Остальная часть кода просто расширяет __file__ (или "./_") до абсолютного пути к файлу, а затем возвращает базовый каталог пути к файлу.
person Simon Streicher    schedule 25.10.2020

Вот частичное решение, все же лучшее, чем все опубликованные на данный момент.

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__))

Теперь это работает для всех вызовов, но если кто-то использует chdir() для изменения текущего каталога, это также не удастся.

Примечания:

  • sys.argv[0] не будет работать, вернет -c, если вы выполните скрипт с python -c "execfile('path-tester.py')"
  • Я опубликовал полный тест на странице https://gist.github.com/1385555, и вы можете улучшить Это.
person sorin    schedule 22.11.2011

Это должно работать в большинстве случаев:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
person Jahid    schedule 14.06.2015
comment
Это решение использует текущий каталог, и в вопросе явно указано, что такое решение не сработает. - person skyking; 06.12.2016

Надеюсь, это поможет: - Если вы запустите сценарий / модуль из любого места, вы сможете получить доступ к переменной __file__, которая является переменной модуля, представляющей расположение сценария.

С другой стороны, если вы используете интерпретатор, у вас нет доступа к этой переменной, где вы получите имя NameError, а os.getcwd() предоставит вам неправильный каталог, если вы запускаете файл из другого места.

Это решение должно дать вам то, что вы ищете, во всех случаях:

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

Я не тестировал его полностью, но он решил мою проблему.

person Mark    schedule 24.03.2017
comment
Это даст файл, а не каталог - person Shital Shah; 17.04.2020

Чтобы получить абсолютный путь к каталогу, содержащему текущий скрипт, вы можете использовать:

from pathlib import Path
absDir = Path(__file__).parent.resolve()

Обратите внимание, что требуется вызов .resolve(), потому что он делает путь абсолютным. Без resolve() вы получите что-то вроде '.'.

В этом решении используется pathlib, который является частью библиотеки Python stdlib начиная с версии v3.4 ( 2014). Это предпочтительнее по сравнению с другими решениями, использующими os.

В официальной документации pathlib есть полезная таблица, сопоставляющая старые функции os с новыми: https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module

person muxator    schedule 11.05.2021

Если __file__ доступен:

# -- script1.py --
import os
file_path = os.path.abspath(__file__)
print(os.path.dirname(file_path))

Для тех, кто хочет иметь возможность запускать команду из интерпретатора или получать путь к месту, из которого вы запускаете скрипт:

# -- script2.py --
import os
print(os.path.abspath(''))

Это работает от интерпретатора. Но при запуске в скрипте (или импортированном) он дает путь к месту, откуда вы запустили скрипт, а не путь к каталогу, содержащему скрипт с печатью.

Пример:

Если ваша структура каталогов

test_dir (in the home dir)
├── main.py
└── test_subdir
    ├── script1.py
    └── script2.py

с участием

# -- main.py --
import script1.py
import script2.py

Результат:

~/test_dir/test_subdir
~/test_dir
person Soap    schedule 04.12.2020

person    schedule
comment
Добавьте больше комментариев, объясните, почему это отвечает на вопрос и чем ваш подход отличается от всех других ответов - person jordanvrtanoski; 16.03.2021