Есть ли способ автоматически закрыть временный файл Python, возвращаемый mkstemp()

Обычно я обрабатываю файлы в Python, используя оператор with, как в этом фрагменте для загрузки ресурса через HTTP:

with (open(filename), "wb"):
    for chunk in request.iter_content(chunk_size=1024):
        if chunk:
            file.write(chunk)
            file.flush()

Но это предполагает, что я знаю имя файла. Предположим, я хочу использовать tempfile.mkstemp(), Эта функция возвращает дескриптор открытого файла и путь, поэтому open в with утверждение будет неверным.

Я немного искал и нашел много предупреждений о том, чтобы быть осторожным в использовании mkstemp должным образом. Несколько статей блога почти кричат, когда говорят, что НЕ выбрасывайте целое число, возвращаемое mkstemp, Существуют дискуссии о том, что файловый дескриптор уровня os отличается от файлового объекта уровня Python. Это нормально, но я не смог найти простейшего шаблона кодирования, который бы гарантировал, что

  • mkstemp вызывается, чтобы получить файл для записи
  • после записи файл Python и его основной файловый дескриптор os полностью закрываются даже в случае исключения. Это именно то поведение, которое мы можем получить с with(open... шаблон.

Итак, мой вопрос, есть ли хороший способ в Python для создания и записи в mkstemp сгенерированный файл, возможно, с использованием другого типа с Statemement, или я должен вручную сделать такие вещи, как fdopen или же closeи т. д. Кажется, для этого должна быть четкая схема.

1 ответ

Решение

Самый простой шаблон кодирования для этого try: / finally::

fd, pathname = tempfile.mkstemp()
try:
    dostuff(fd)
finally:
    os.close(fd)

Однако, если вы делаете это более одного раза, обернуть это в контекстном менеджере тривиально:

@contextlib.contextmanager
def mkstemping(*args):
    fd, pathname = tempfile.mkstemp(*args)
    try:
        yield fd
    finally:
        os.close(fd)

И тогда вы можете просто сделать:

with mkstemping() as fd:
    dostuff(fd)

Если вы действительно хотите, конечно, вы всегда можете обернуть fd в файловый объект (передав его open, или же os.fdopen в старых версиях). Но... зачем идти на дополнительные неприятности? Если вы хотите fd, используйте его как fd.

И если вы не хотите fd, если у вас нет веской причины, которая вам нужна mkstemp вместо более простого и более высокого уровня NamedTemporaryFile Вы не должны использовать низкоуровневый API. Просто сделай это:

with tempfile.NamedTemporaryFile(delete=False) as f:
    dostuff(f)

Помимо того, что проще with это также имеет то преимущество, что это уже объект файла Python, а не просто дескриптор файла ОС (а в Python 3.x это может быть текстовый файл Unicode).


Еще более простое решение - полностью избежать временного файла.

Почти во всех синтаксических анализаторах XML есть способ разбирать строку вместо файла. С cElementTree это просто вопрос вызова fromstring вместо parse, Итак, вместо этого:

req = requests.get(url)
with tempfile.NamedTemporaryFile() as f:
    f.write(req.content)
    f.seek(0)
    tree = ET.parse(f)

... просто сделай это:

req = requests.get(url)
tree = ET.fromstring(req.content)

Конечно, первая версия должна хранить документ XML и проанализированное дерево в памяти один за другим, в то время как вторая должна хранить их оба одновременно, так что это может увеличить пиковое использование памяти примерно на 30%. Но это редко проблема.

Если это проблема, многие библиотеки XML имеют способ подачи данных по мере их поступления, а многие загружаемые библиотеки имеют способ потоковой передачи данных по битам - и, как вы можете себе представить, это опять-таки верно для cElementTree. XMLParser и для requests несколькими разными способами. Например:

req = requests.get(url, stream=True)
parser = ET.XMLParser()
for chunk in iter(lambda: req.raw.read(8192), ''):
    parser.feed(chunk)
tree = parser.close()

Не так просто, как просто fromstring … Но это все же проще, чем использование временного файла, и, вероятно, более эффективно для загрузки.

Если это использование формы с двумя аргументами iter вас смущает (многим кажется, что поначалу трудно это понять), вы можете переписать это так:

req = requests.get(url, stream=True)
parser = ET.XMLParser()
while True:
    chunk = req.raw.read(8192)
    if not chunk:
        break
    parser.feed(chunk)
tree = parser.close()
Другие вопросы по тегам