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

Я хотел бы посмотреть, как лучше определить текущий каталог скриптов в 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(),

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

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

18 ответов

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

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

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

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

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

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))

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

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
#!/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-файла (/ egg). Работает, если скрипт "импортирован" (PYTHONPATH=/path/to/library.zip python -mscript_to_run) из почтового файла; в этом случае он возвращает путь к архиву. Работает, если скрипт скомпилирован в отдельный исполняемый файл (sys.frozen). Работает по символическим ссылкам (realpath устраняет символические ссылки). Работает в интерактивном переводчике; в этом случае он возвращает текущий рабочий каталог.

os.path... подход был "готово" в Python 2.

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

from pathlib import Path
cwd = Path(__file__).parents[0]

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

$ pip install locate

>>> from locate import this_dir
>>> print(this_dir())
C:/Users/Heetbeet

За .py скрипты, а также интерактивное использование:

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

  • При запуске или импорте .pyfile, базовый каталог файла. Это всегда правильный путь.
  • При запуске .ipynnotebook, текущий рабочий каталог. Это всегда правильный путь, поскольку 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__ (или же "./_") в абсолютный путь к файлу, а затем возвращает базовый каталог пути к файлу.

Было бы

import os
cwd = os.getcwd()

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

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

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

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

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

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

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

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

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

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
import os
import sys

def get_script_path():
    return os.path.dirname(os.path.realpath(sys.argv[0]))


my_script_dir = get_script_path()
print my_script_dir

Это дает вам каталог скрипта в верхней части стека (то есть тот, который выполняется - не Python, который обычно выполняется первым, возвращая C:/)

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

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

Вот и все, что у меня есть на макушке.

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

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 и вы можете его улучшить.

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

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

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

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

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

Если __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

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

      dir = '/'.join(__file__.split('/')[:-1])
print(dir)

Если скрипт включен /path/to/script.pyто это напечатает. Обратите внимание, что это вызовет ошибку на терминале, поскольку ни один файл не выполняется. Это в основном анализирует каталог из __file__удаление последней его части. В таком случае /script.pyудаляется для получения вывода /path/to.

      print(__import__("pathlib").Path(__file__).parent)

Просто pwd
на ноутбуке Jupyter: pwd + shift + enter
у шпионов: pwd + F9

Другие вопросы по тегам