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


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.