TATECHATLAS
◎ Русский
Программирование

Относительные пути в Python: запуск из терминала, IDE и служб

Разрешение относительных путей в Python зависит от рабочего каталога процесса, который различается в зависимости от способа запуска. В этом руководстве показано, как проверить рабочий каталог, выбрать и задокументировать явную базу путей, а также понять, почему __file__ не всегда можно доверять.

В этом материале

Относительный путь, такой как Path("data/config.json"), разрешается относительно текущего рабочего каталога процесса, а не расположения вашего скрипта. Рабочий каталог - это то, что возвращает os.getcwd() при запуске интерпретатора, и терминал, IDE и менеджер служб обычно устанавливают его по-разному. Выведите Path.cwd() при запуске, чтобы увидеть реальное значение. Затем осознанно выберите явную базу: рабочий каталог для файлов, предоставленных пользователем, каталог файла __file__ для встроенных ресурсов или настроенный абсолютный путь для служб. Поскольку __file__ является необязательным атрибутом и может отсутствовать для некоторых модулей, защитите его с помощью getattr. absolute() и resolve() в pathlib различаются: resolve() также устраняет «..» и следует символическим ссылкам. Универсального значения по умолчанию, работающего везде, не существует; проверьте каждую среду запуска и запишите в журнал как рабочий каталог, так и разрешённый путь.

Почему рабочий каталог управляет относительными путями

Путь вроде data/config.json не интерпретируется самой Python; он передается операционной системе, которая разрешает его относительно текущего рабочего каталога процесса. Этот каталог устанавливается один раз при запуске процесса и не следует за вашим скриптом. pathlib.Path.cwd() возвращает новый объект пути, представляющий текущий каталог, как возвращает os.getcwd(). Таким образом, один и тот же каталог может дать один результат в терминале, другой в IDE и третий под менеджером служб. Первым шагом при диагностике ошибки отсутствия файла никогда не является догадка о расположении скрипта, а запись того, что процесс действительно видит как свой каталог.

import os
from pathlib import Path

# 1. Inspect what the launcher really set.
print("cwd:", os.getcwd())
print("cwd path:", Path.cwd())
print("script dir:", Path(__file__).resolve().parent)

Выбор явной базы для каждого контекста запуска

Как только вы узнаете рабочий каталог, осознанно решите, какую базу вы хотите использовать. Для входных данных, предоставленных пользователем, рабочий каталог часто является правильным выбором, потому что пользователь ожидает, что пути будут относительными к тому месту, откуда запущен инструмент. Для встроенных ресурсов, которые путешествуют вместе с вашим кодом, основывайте путь на расположении скрипта или пакета вместо этого. Для долго работающих служб предпочитайте настроенный абсолютный путь, чтобы служба вела себя одинаково независимо от того, как менеджер процессов её запускает. Задокументируйте выбранную конвенцию рядом с кодом, который открывает файл, потому что будущие сопровождающие иначе предположат, что расположение скрипта является значением по умолчанию.

Осторожное использование __file__, когда оно доступно

Атрибут модуля __file__ может указывать на файл, который определил текущий код, что делает его полезным для поиска встроенных ресурсов. В документации по модели данных предупрещается, что __file__ является необязательным и может отсутствовать для некоторых модулей, включая статически связанные C-модули или модули, загруженные из необычных источников. Защитите доступ с помощью getattr, чтобы код не вызывал ошибку при отсутствии атрибута. Когда __file__ присутствует, разрешите его в абсолютную форму перед выведением соседних путей, потому что относительный __file__ всё ещё зависел бы от рабочего каталога.

absolute() против resolve() в pathlib

pathlib предлагает два способа сделать путь абсолютным, и они не взаимозаменяемы. Path.absolute() делает путь абсолютным без нормализации или разрешения символических ссылок, что ближе к os.path.abspath(), но всё ещё сохраняет сегменты '..' для безопасности. Path.resolve() делает путь абсолютным, устраняет компоненты '..' и следует символическим ссылкам, что ближе к os.path.realpath(). Если вам нужна реальное расположение существующего файла, resolve() обычно является лучшим выбором. Если вам нужна только стабильная абсолютная форма без касания файловой системы, absolute() может быть предпочтительнее.

Запись в журнал рабочего каталога и разрешённого пути

Когда файл не найден, записывайте в журнал как рабочий каталог, так и точный путь, который вы пытались открыть. Эта пара обычно объясняет сбой быстрее, чем догадки о расположении скрипта. При возможности включайте контекст запуска, например, запущен ли процесс из терминала, конфигурации запуска IDE или менеджера служб. Если путь зависит от конфигурации, также записывайте значение конфигурации. Эти записи делают возможным воспроизведение среды позже вместо предположения о универсальном значении по умолчанию.

Различия между распространёнными запускателями, которые нужно проверить

Запуски из терминала часто начинаются с текущего каталога оболочки, который может быть корнем проекта или папкой, в которую вы перешли по cd. Конфигурации запуска IDE могут устанавливать рабочий каталог в корень проекта, папку модуля или пользовательское значение, определённое в настройках запуска. Менеджеры служб и точки входа контейнеров могут запускать процесс в системном каталоге или объявленном рабочем каталоге, который отличается от расположения кода. Поскольку эти значения по умолчанию различаются, тестируйте фактический каталог запуска в каждой среде, а не полагайтесь на поведение одной машины.

Практическая проверка запуска для кода, чувствительного к путям

Для приложений, которые открывают файлы на раннем этапе, добавьте небольшую проверку запуска, которая выводит или записывает в журнал рабочий каталог и базовый путь, который вы намерены использовать. Если база берётся из __file__, убедитесь, что атрибут существует, и разрешите его перед использованием. Если база берётся из конфигурации, подтвердите, что настроенный путь является абсолютным, или задокументируйте, как он будет интерпретироваться. Эта проверка особенно полезна в автоматизированных средах, где каталог запуска не очевиден из исходного кода.

Ограничения и соображения версий

Описанное здесь поведение зависит от рабочего каталога процесса и от того, установлен ли __file__, и то, и другое может различаться между реализациями Python и средами запуска. Нормализация pathlib также может изменять то, как путь интерпретируется другими инструментами, поэтому pathlib не является полной заменой os.path в каждом сценарии. Некоторые методы pathlib изменились в последних версиях Python, включая более строгую обработку циклов символических ссылок и зарезервированных путей, поэтому проверьте поведение на целевой среде выполнения. Когда важна переносимость, предпочитайте явные абсолютные базы и не предполагайте, что относительные пути будут разрешаться одинаково везде.

Что проверить

  • Выведите Path.cwd() или os.getcwd() при запуске, чтобы подтвердить фактический рабочий каталог для каждого запускателя.
  • Используйте явную базу для относительных путей: рабочий каталог для файлов пользователя, каталог файла __file__ для встроенных ресурсов или настроенный абсолютный путь для служб.
  • Защитите __file__ с помощью getattr, потому что он является необязательным и может отсутствовать для некоторых модулей.
  • Выбирайте resolve() в pathlib, когда нужно устранить '..' и следовать символическим ссылкам, и absolute(), когда нужна только абсолютная форма без нормализации.

Разрешение относительных путей зависит от рабочего каталога процесса, который различается между запусками из терминала, IDE и служб. __file__ является необязательным и может отсутствовать для некоторых модулей. absolute() и resolve() в pathlib ведут себя по-разному, а нормализация pathlib может изменять то, как пути интерпретируются другими инструментами. Некоторые поведения pathlib также изменились в последних версиях Python, поэтому проверьте на целевой среде выполнения.

Источники

  1. Python: pathlib current directory and path resolution ↗
  2. Python: os working directory and environment ↗
  3. Python: module file attribute ↗
Наверх ↑