Чтение файла с использованием относительного пути в проекте python

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

project
    /data
        test.csv
    /package
        __init__.py
        module.py
    main.py

__init__.py:

from .module import test

module.py:

import csv

with open("..data/test.csv") as f:
    test = [line for line in csv.reader(f)]

main.py:

import package

print(package.test)

Когда я запускаю main.py, я получаю следующую ошибку:

 C:\Users\Patrick\Desktop\project>python main.py
Traceback (most recent call last):
  File "main.py", line 1, in <module>
    import package
  File "C:\Users\Patrick\Desktop\project\package\__init__.py", line 1, in <module>
    from .module import test
  File "C:\Users\Patrick\Desktop\project\package\module.py", line 3, in <module>
    with open("../data/test.csv") as f:
FileNotFoundError: [Errno 2] No such file or directory: '../data/test.csv'

Однако, если я запускаю module.py из каталога package, я не получаю ошибок. Таким образом, кажется, что относительный путь, используемый в open(...), относится только к тому, где выполняется исходный файл (i.e __name__ == "__main__")? Я не хочу использовать абсолютные пути. Каковы некоторые способы борьбы с этим?

Ответ 1

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

Но есть часто используемый прием для построения абсолютного пути из текущего скрипта: используйте его специальный атрибут __file__ :

import csv
import os.path

my_path = os.path.abspath(os.path.dirname(__file__))
path = os.path.join(my_path, "../data/test.csv")
with open(path) as f:
    test = list(csv.reader(f))

Обратите внимание, что в python 3.4 __file__ всегда является абсолютным для импортированных модулей, и вы можете удалить часть os.path.abspath в этом примере. Не то чтобы это строго необходимо, но это позволяет избежать неожиданностей, если вы в какой-то момент измените текущий рабочий каталог и ваш модуль был импортирован с использованием относительного пути.

Ответ 2

Для Python 3. 4+:

import csv
from pathlib import Path

base_path = Path(__file__).parent
file_path = (base_path / "../data/test.csv").resolve()

with open(file_path) as f:
    test = [line for line in csv.reader(f)]

Ответ 3

Моя версия Python - Python 3.5.2, и решение, предложенное в принятом ответе, не сработало для меня. Мне все еще сообщили об ошибке

FileNotFoundError: [Errno 2] No such file or directory

когда я запускал my_script.py из терминала. Хотя он работал нормально, когда я запускал его через Run/Debug Configurations из PyCharm IDE (PyCharm 2018.3.2 (Community Edition)).

Решение:

вместо использования:

my_path = os.path.abspath(os.path.dirname(__file__)) + some_rel_dir_path 

как указано в принятом ответе, я использовал:

my_path = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) + some_rel_dir_path

Объяснение: Изменение os.path.dirname(__file__) на os.path.dirname(os.path.abspath(__file__)) решает следующую проблему:

Когда мы запускаем наш скрипт так: python3 my_script.py переменная __file__ имеет только строковое значение my_script.py без пути, ведущего к этому конкретному сценарию. Вот почему метод dirname(__file__) возвращает пустую строку "". Это также резон, почему my_path = os.path.abspath(os.path.dirname(__file__)) + some_rel_dir_path на самом деле то же самое, что и my_path = some_rel_dir_path. Следовательно, FileNotFoundError: [Errno 2] No such file or directory дается при попытке использовать метод open, потому что нет такого каталога, как "some_rel_dir_path".

Запуск сценария из PyCharm IDE Запуск/отладка конфигураций сработал, потому что он запускает команду python3 /full/path/to/my_script.py (где "/full/path/to" указан нами в переменной "Рабочий каталог" в конфигурациях Run/Debug) вместо просто python3 my_script.py как это делается, когда мы запускаем его из терминала.

Надеюсь, что это будет полезно.