Вопрос

У меня есть модуль кэширования urllib2, который периодически выходит из строя из-за следующего кода:

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)

Проблема в том, что к моменту выполнения второй строки папка может существовать и выдать ошибку:

  File ".../cache.py", line 103, in __init__
    os.mkdir(self.cache_location)
OSError: [Errno 17] File exists: '/tmp/examplecachedir/'

Это связано с тем, что скрипт одновременно запускается множество раз с помощью стороннего кода, который я не контролирую.

Код (до того, как я попытался исправить ошибку) можно найти здесь, на гитхабе

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

Я не хочу просто сбрасывать ошибку, так как возникает та же ошибка Errno 17, если имя папки существует в виде файла (другая ошибка), например:

$ touch blah
$ python
>>> import os
>>> os.mkdir("blah")
Traceback (most recent call last):
  File "", line 1, in 
OSError: [Errno 17] File exists: 'blah'
>>>

я не могу использовать threading.RLock поскольку код вызывается из нескольких процессов.

Итак, я попробовал написать простую файловую блокировку (эту версию можно найти здесь), но здесь есть проблема:он создает файл блокировки на один уровень выше, поэтому /tmp/example.lock для /tmp/example/, который ломается, если вы используете /tmp/ в качестве каталога кэша (поскольку он пытается сделать /tmp.lock)..

Короче говоря, мне нужно кэшировать urllib2 ответы на диск.Для этого мне нужно получить доступ к известному каталогу (создав его, если необходимо) безопасным многопроцессорным способом.Он должен работать на OS X, Linux и Windows.

Мысли?Единственное альтернативное решение, которое я могу придумать, — это переписать модуль кэша, используя хранилище SQLite3, а не файлы.

Это было полезно?

Решение

В Python 3.x вы можете использовать os.makedirs(path, exists_ok=True), что не вызовет никаких исключений, если такой каталог существует.Это поднимет FileExistsError: [Errno 17] если существует файл с тем же именем, что и запрошенный каталог (path).

Проверьте это с помощью:

import os

parent = os.path.dirname(__file__)

target = os.path.join(parent, 'target')

os.makedirs(target, exist_ok=True)
os.makedirs(target, exist_ok=True)

os.rmdir(target)

with open(target, 'w'):
    pass

os.makedirs(target, exist_ok=True)

Другие советы

Вместо

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)

ты мог бы сделать

try:
    os.makedirs(self.cache_location)
except OSError:
    pass

Поскольку вы получите то же самое функциональность.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ:Я не знаю, насколько это может быть Pythonic.


С использованием SQLite3, мощь это немного перебор, но я бы добавил много функциональности и гибкости в соответствии с вашим вариантом использования.

Если вам приходится много «выбирать», одновременно вставлять и фильтровать, отличная идея использовать SQLite3, поскольку он не усложняет простые файлы (можно утверждать, что он усложняет).


Перечитывая ваш вопрос (и комментарии), я могу лучше понять вашу проблему.

Какова вероятность того, что файл могло бы создать такое же состояние гонки?

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

if not os.path.isfile(self.cache_location):
    try:
        os.makedirs(self.cache_location)
    except OSError:
        pass

Кроме того, читая ваш код, я бы изменил

else:
    # Our target dir is already a file, or different error,
    # relay the error!
    raise OSError(e)

к

else:
    # Our target dir is already a file, or different error,
    # relay the error!
    raise

поскольку это действительно то, что вы хотите, Python повторно вызовет то же самое исключение (просто придирки).


Еще одна вещь, может быть этот может быть вам полезен (только для Unix).

Код, который у меня получился, был:

import os
import errno

folder_location = "/tmp/example_dir"

try:
    os.mkdir(folder_location)
except OSError as e:
    if e.errno == errno.EEXIST and os.path.isdir(folder_location):
        # File exists, and it's a directory,
        # another process beat us to creating this dir, that's OK.
        pass
    else:
        # Our target dir exists as a file, or different error,
        # reraise the error!
        raise

Не могли бы вы уловить исключение, а затем проверить, существует ли файл как каталог или нет?

Когда у вас возникают состояния гонки, EAFP (проще попросить прощения, чем разрешения) работает лучше, чем LBYL (посмотрите, прежде чем прыгать)

Стратегии проверки ошибок

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top