Як правильно визначити поточний каталог скриптів?


260

Я хотів би побачити, який найкращий спосіб визначити поточний каталог скриптів у 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().

Увага

Будь-яке рішення, яке використовує поточний каталог, вийде з ладу, це може відрізнятися залежно від способу виклику сценарію або його можна змінити всередині запущеного сценарію.


1
Чи можете ви бути більш конкретними, звідки вам потрібно знати, звідки походить файл? - у коді, який імпортує файл (включаючи відомий хост) або у імпортованому файлі? (самовільний раб)
synthesizerpatel

3
Див Ron кальян в pathlibрішення , якщо ви використовуєте Python 3.4 або вище: stackoverflow.com/a/48931294/1011724
Dan

Отже, рішення НЕ використовувати будь-який поточний каталог у коді, а використовувати якийсь конфігураційний файл?
ZhaoGang

Цікаве відкриття, яке я щойно зробив: коли це python myfile.pyробиться з оболонки, вона працює, але :!python %і :!python myfile.pyзвідси, і зсередини vim не вдається з допомогою системи не може знайти вказаний шлях. Це досить дратує. Чи може хтось прокоментувати причину цього та потенційні шляхи вирішення?
inVader

Відповіді:


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

справді найкраще, що ти збираєшся отримати.

Незвично виконувати сценарій з exec/ execfile; як правило, ви повинні використовувати інфраструктуру модуля для завантаження скриптів. Якщо вам потрібно скористатися цими методами, я пропоную встановити __file__в globalsпереході до сценарію, щоб він міг прочитати це ім'я файлу.

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


2
Ніколи не кажи ніколи? Відповідно до цього: stackoverflow.com/a/18489147 відповідь на кросплатформенне рішення abspath (getourcefile (lambda: 0))? Або ще щось мені не вистачає?
Джефф Еллен

131

Якщо ви дійсно хочете висвітлити випадок, через який викликається скрипт execfile(...), ви можете використовувати inspectмодуль для виведення імені файлу (включаючи шлях). Наскільки мені відомо, це буде працювати у всіх перелічених вами випадках:

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

4
Я думаю, що це дійсно найбільш надійний метод, але я сумніваюся в заявленій необхідності цього в ОП. Я часто бачу, що розробники роблять це, коли вони використовують файли даних у місцях, що відносяться до виконавчого модуля, але файли даних IMO слід розміщувати у відомому місці.
Райан Гінстром

14
@Ryan LOL, якщо було б чудово, якби ви змогли визначити "відоме місце", яке є мультиплатформою і яке також поставляється з модулем. Я готовий зробити ставку, що єдине безпечне місце розташування - це сценарій розташування. Зауважте, що це не означає, що сценарій повинен писати до цього місця, але для читання даних це безпечно.
sorin

1
І все-таки рішення не добре, просто спробуйте зателефонувати chdir()перед функцією, це змінить результат. Також виклик сценарію python з іншого каталогу змінить результат, тому це не гарне рішення.
sorin

2
os.path.expanduser("~")- це платформний спосіб отримати каталог користувача. На жаль, це не найкраща практика Windows щодо того, де зберігати дані програми.
Райан Гінстром

6
@sorin: я намагався chdir()перед запуском сценарію; це дає правильний результат. Я спробував викликати скрипт з іншого каталогу, і він також працює. Результати такі ж, як і на inspect.getabsfile()основі рішення .
jfs

43
#!/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виключає символічні посилання). Він працює в інтерактивному перекладачі; він повертає поточний робочий каталог у цьому випадку.


Чудово працює з PyInstaller.
габоровий

1
Чи є якась причина, чому getabsfile(..)вона не згадується в документаціїinspect ? Він з’являється у джерелі, пов’язаному із цією сторінкою.
Євгеній Сергєєв

@EvgeniSergeev це може бути помилка. Це проста обгортка навколо getsourcefile(), getfile()яка задокументована.
jfs

24

У Python 3.4+ ви можете скористатися більш простим pathlibмодулем:

from inspect import currentframe, getframeinfo
from pathlib import Path

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

2
Відмінна простота!
Cometsong

3
Можливо, ви можете використовувати Path(__file__)( inspectмодуль не потребує ).
Пеке

@Переробка, яка створює шлях, що включає ім'я поточного файлу, а не батьківський каталог. Якщо я намагаюся отримати поточний каталог скриптів для того, щоб вказати на файл у тій самій папці, наприклад, очікуючи завантажити файл конфігурації в той самий каталог, що і сценарій, Path(__file__)дає, /path/to/script/currentscript.pyколи ОП хотів отримати/path/to/script/
Давос

8
О, я неправильно зрозумів, ви маєте на увазі уникати модуля перевірки і просто використовувати щось на кшталт parent = Path(__file__).resolve().parent Це набагато приємніше.
Давос

3
@Dut A. Ви повинні використовувати для цього .joinpath()(чи /оператора), а не +.
Євген Ярмаш

13

Цей os.path...підхід був «зробленою справою» в Python 2.

У Python 3 ви можете знайти каталог скриптів наступним чином:

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

11
Або просто Path(__file__).parent. Але cwdце неправильне значення, це не поточний робочий каталог , а каталог файлу . Вони могли бути однаковими, але це зазвичай не так.
Нуно Андре

5

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

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


2
Якщо ви не працюєте з exec (), ви втратите контекст налагодження. Також, exec (), як передбачається, буде значно швидшим, ніж запуск нового процесу.
sorin

@sorin Це не питання exec проти початку нового процесу, тому це слабкий аргумент. Це питання exec vs використання імпорту або виклику функції.
Вім

4

Би

import os
cwd = os.getcwd()

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


3
Це не допоможе. Я вважаю, @bogdan шукає каталог для сценарію, який знаходиться у верхній частині стека викликів. тобто у всіх його випадках вона повинна надрукувати каталог, у якому 'myfile.py' сидить. І все-таки ваш метод буде друкувати лише каталог файлу, що викликає exec('myfile.py'), як __file__і sys.argv[0].
Чжан18

Так, це має сенс. Я просто хотів переконатися, що @bogdan не випускає з уваги щось просте, і я не міг точно сказати, чого вони хочуть.
Буде Маккутхен

3

По-перше, тут відсутня пара випадків використання, якщо ми говоримо про способи введення анонімного коду ..

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 та імпорту. Код, виконаний у цьому обмеженому середовищі, матиме доступ лише до модулів та функцій, які вважаються безпечними; Ви можете підклас 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, а не "імпорт". Для підключення навантаження на модуль вищого рівня, можливо, ви зможете використовувати sys.path_hooks (Ввічливість з PyMOTW).

Це все, що у мене з голови.


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, і ви можете покращити його.

1

Це повинно працювати в більшості випадків:

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

5
У цьому рішенні використовується поточний каталог, і в питанні прямо вказано, що таке рішення не вдасться.
злетіння

1

Сподіваємось, це допомагає: - Якщо ви запускаєте скрипт / модуль з будь-якого місця, ви зможете отримати доступ до __file__змінної, яка є змінною модуля, що представляє розташування сценарію.

З іншого боку, якщо ви використовуєте інтерпретатор, ви не маєте доступу до цієї змінної, де ви отримаєте ім’я NameErrorта os.getcwd()надасте вам неправильну директорію, якщо ви запускаєте файл з іншого місця.

Це рішення має дати вам те, що ви шукаєте у всіх випадках:

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

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


Це дасть файл, а не каталог
Shital Shah
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.