Есть ли способ автоматически закрыть временный файл 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()