185


51

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

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

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

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

13 Answer


168


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

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

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

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


112


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

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


38


#!/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 потерпят неудачу). Это работает, если скрипт находится внутри an исполняемый zip-файл (/ яйцо). Это работает, если скрипт «импортирован» (PYTHONPATH = / path / to / library.zip python -mscript_to_run) из zip-файла; в этом случае он возвращает путь к архиву. Это работает, если скрипт скомпилирован в отдельный исполняемый файл (sys.frozen). Это работает для символических ссылок (realpath исключает символические ссылки). Работает в интерактивном переводчике; в этом случае он возвращает текущий рабочий каталог.


18


В Python 3.4+ вы можете использовать более простой модуль https://docs.python.org/3/library/pathlib.html [pathlib]:

from inspect import currentframe, getframeinfo
from pathlib import Path

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


5


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

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


3


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

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

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

Если вы заинтересованы в security, имя файла, которое импортируется через exec / execfile, не имеет значения - вам следует использовать http://docs.python.org/library /rexec.html[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/)
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).

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


3


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

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


3


Подход "os.path …​" был "готовым делом" в Python 2.

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

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


2


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

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 и вы добро пожаловать, чтобы улучшить это.


2


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