Как ссылаться на относительные пути ресурсов при работе с хранилищем кода

188

Мы работаем с хранилищем кода, которое развернуто как в Windows, так и в Linux - иногда в разных каталогах. Как один из модулей внутри проекта должен ссылаться на один из не-Python-ресурсов в проекте (файлы CSV и т. Д.)?

Если мы сделаем что-то вроде:

thefile=open('test.csv')

или:

thefile=open('../somedirectory/test.csv')

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

То, что я хотел бы сделать, это что-то вроде:

path=getBasePathOfProject()+'/somedirectory/test.csv'
thefile=open(path)

Является ли это возможным?

olamundo
источник

Ответы:

255

Попробуйте использовать имя файла относительно текущего пути к файлам. Пример для './my_file':

fn = os.path.join(os.path.dirname(__file__), 'my_file')

В Python 3.4+ вы также можете использовать pathlib :

fn = pathlib.Path(__file__).parent / 'my_file'
c089
источник
3
Я думаю, что это решение будет работать, только если ресурс находится в том же каталоге файла Python или в его подкаталоге. Как вы решаете это, когда у вас есть следующая древовидная структура: / Project_Root_dir / python_files_dir / Еще несколько подкаталогов здесь py_file.py / resources / Некоторые подкаталоги здесь resource_file.csv
olamundo
1
Извините, дерево файлов искажено в этом последнем сообщении ... вторая попытка: у вас есть файл по адресу /Project_Root_dir/python_files_dir/some_subdirs/py_file.py и у вас есть файл ресурсов по адресу /Project_Root_dir/resources/some_subdirs/resource_file.csv
olamundo
28
Вы должны быть в состоянии добраться до родительского каталога, используя join (foo, '..'). Поэтому из / root / python_files / module / myfile используйте os.path.join (os.path.dirname ( __file__), '..', '..', 'resources')
c089
7
os.pardirнемного лучше, чем '..', хотя оба они эквивалентны как для POSIX, так и для Windows.
Дэвид Чэмберс
4
@cedbeu: Это эквивалентно на каждой системе, с которой я когда-либо сталкивался, и я думаю, что каждый системный питон работает сегодня (пожалуйста, поправьте меня, если я ошибаюсь здесь). Однако, если вы ожидаете, что Python будет перенесен в систему с использованием другого разделителя пути в будущем, и хотите, чтобы ваш код был к этому готов, os.pardir будет более переносимым. Я бы сказал, что каждый программист, даже тот, кто никогда не читает ни одного Python, знает значение «..», тогда как «os.pardir» - это уровень косвенности, который мне придется искать в документации, так что лично я буду придерживаться "..".
c089
41

Если вы используете инструменты установки или распространяете (установка setup.py), тогда «правильный» способ получить доступ к этим упакованным ресурсам - использовать package_resources.

В вашем случае пример будет

import pkg_resources
my_data = pkg_resources.resource_string(__name__, "foo.dat")

Который, конечно, читает ресурс, и считанные двоичные данные будут значением my_data

Если вам просто нужно имя файла, вы также можете использовать

resource_filename(package_or_requirement, resource_name)

Пример:

resource_filename("MyPackage","foo.dat")

Преимущество заключается в том, что он гарантированно работает, даже если это архивное распространение, как яйцо.

См. Http://packages.python.org/distribute/pkg_resources.html#resourcemanager-api.

Шарун Томас
источник
3
Я знаю, что это старый ответ, мой предпочтительный способ (/ возможно, был?) Использовать pkg_resources, но с исчезновением яиц на молнии, есть ли вред при использовании, __file__как в старые добрые времена?
Пиклер
1
Это солидный подход. Даже если соглашение о яйцах уходит, setuptools - нет, и многие все еще устанавливают deps против git-репозиториев, где яйцо
создается
18

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

Использование абсолютного пути должно быть лучшим решением:

import os
package_dir = os.path.dirname(os.path.abspath(__file__))
thefile = os.path.join(package_dir,'test.cvs')
skyfree
источник
15

Я часто использую что-то похожее на это:

import os
DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'datadir'))

# if you have more paths to set, you might want to shorten this as
here = lambda x: os.path.abspath(os.path.join(os.path.dirname(__file__), x))
DATA_DIR = here('datadir') 

pathjoin = os.path.join
# ...
# later in script
for fn in os.listdir(DATA_DIR):
    f = open(pathjoin(DATA_DIR, fn))
    # ...

Переменная

__file__

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

  • путь абсолютен, но все же относителен
  • проект все еще может быть развернут в относительном контейнере

Но вам нужно следить за совместимостью платформы - Windows os.pathsep отличается от UNIX.

user137673
источник
5
import os
cwd = os.getcwd()
path = os.path.join(cwd, "my_file")
f = open(path)

Вы также пытаетесь нормализовать свое cwdиспользование os.path.abspath(os.getcwd()). Больше информации здесь .

gavoja
источник
3
очень мало случаев использования, где cwdпуть модуля, тем не менее
cedbeu
он не работает внутри пакета, только из того же каталога (или рабочего каталога), который установлен скриптом.
Александра
Это не будет работать, если пользователь запускает программу, используя абсолютный путь из другого каталога. например, python3 /usr/someone/test.py
sgrpwr
2

Вы можете использовать __file__переменную build in . Содержит путь к текущему файлу. Я бы реализовал getBaseOfProject в модуле в корне вашего проекта. Там я бы получил часть пути __file__и вернул бы это. Этот метод может быть использован везде в вашем проекте.

Ахим
источник
0

Я немного озадачен здесь. Хотел упаковать некоторые файлы ресурсов в файл wheel и получить к ним доступ. Выполнял упаковку, используя файл манифеста, но pip install не устанавливал его, если это не был подкаталог. Надеемся, что эти снимки помогут

├── cnn_client
   ├── image_preprocessor.py
   ├── __init__.py
   ├── resources
      ├── mscoco_complete_label_map.pbtxt
      ├── retinanet_complete_label_map.pbtxt
      └── retinanet_label_map.py
   ├── tf_client.py

MANIFEST.in

recursive-include cnn_client/resources *

Создал weel используя стандартный setup.py. Пип установил файл колеса. После установки проверил, установлены ли ресурсы. Они есть

ls /usr/local/lib/python2.7/dist-packages/cnn_client/resources

mscoco_complete_label_map.pbtxt
retinanet_complete_label_map.pbtxt 
 retinanet_label_map.py  

В tfclient.py для доступа к этим файлам. из

templates_dir = os.path.join(os.path.dirname(__file__), 'resources')
 file_path = os.path.join(templates_dir, \
            'mscoco_complete_label_map.pbtxt')
        s = open(file_path, 'r').read()

И это работает.

Алекс Пуннен
источник
-5

Я потратил много времени на то, чтобы найти ответ на этот вопрос, но, наконец, я его получил (и это действительно очень просто):

import sys
import os
sys.path.append(os.getcwd() + '/your/subfolder/of/choice')

# now import whatever other modules you want, both the standard ones,
# as the ones supplied in your subfolders

Это добавит относительный путь вашей подпапки в каталоги для Python, чтобы посмотреть в Это довольно быстро и грязно, но это работает как шарм :)

Рутгер Семп
источник
6
Это будет работать, только если вы запускаете программу Python из той же директории, что и рассматриваемый файл .py. И в этом случае вы могли бы просто сделать в open('your/subfolder/of/choice')любом случае.
Пол Фишер
4
и ОП упомянул, что код должен работать как в Windows, так и в Linux. Этого не будет.
user183037