Как создать временный файл с кодировкой Unicode?

Когда я использую open() для открытия файла, я не могу писать строки unicode. Я узнал, что мне нужно использовать codecs и открыть файл с кодировкой Unicode (см. http://docs.python.org/howto/unicode.html#reading-and-writing-unicode-data).

Теперь мне нужно создать несколько временных файлов. Я попытался использовать библиотеку tempfile, но у нее нет какой-либо опции кодирования. Когда я пытаюсь написать любую строку юникода во временном файле с tempfile, он терпит неудачу:

#!/usr/bin/python2.6
# -*- coding: utf-8 -*-
import tempfile
with tempfile.TemporaryFile() as fh:
  fh.write(u"Hello World: ä")
  fh.seek(0)
  for line in fh:
    print line

Как создать временный файл с кодировкой Unicode в Python?

Edit:

  • Я использую Linux, и сообщение об ошибке, которое я получаю для этого кода:

    Traceback (most recent call last):
      File "tmp_file.py", line 5, in <module>
        fh.write(u"Hello World: ä")
    UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 13: ordinal not in range(128)
    
  • Это просто пример. На практике я пытаюсь написать строку, возвращаемую некоторым API.

Ответы

Ответ 1

Все остальные ответы правильные, я просто хочу уточнить, что происходит:

Различие между буквальным 'foo' и литералом u'foo 'состоит в том, что первая строка представляет собой строку байтов, а вторая - объект Unicode

Во-первых, поймите, что Unicode - это набор символов. UTF-8 является кодировкой. Объект Unicode относится к первому - это строка Unicode, не обязательно UTF-8. В вашем случае кодировка для строкового литерала будет UTF-8, поскольку вы указали ее в первых строках файла.

Чтобы получить строку Unicode из строки байта, вы вызываете метод .encode

>>>> u"ひらがな".encode("utf-8") == "ひらがな"
True

Аналогично, вы можете вызвать свой string.encode в вызове write и добиться того же эффекта, что и удаление u.

Если вы не указали кодировку в верхней части экрана, скажем, если вы читали данные Unicode из другого файла, вы должны указать, какая кодировка была до того, как она достигнет строки Python. Это определит, как оно будет представлено в байтах (т.е. Тип str).

Ошибка, которую вы получаете, заключается только в том, что модуль tempfile ожидает объект str. Этот не означает означает, что он не может обрабатывать unicode, просто он ожидает, что вы передадите байтовую строку, а не объект Unicode, - потому что, не указав кодировку, она не знает, как напишите его в файл temp.

Ответ 2

Я выяснил одно решение: создайте временный файл, который не будет автоматически удален с помощью tempfile, закройте его и снова откройте с помощью codecs:

#!/usr/bin/python2.6
# -*- coding: utf-8 -*-

import codecs
import os
import tempfile

f = tempfile.NamedTemporaryFile(delete=False)
filename = f.name
f.close()

with codecs.open(filename, 'w+b', encoding='utf-8') as fh:
  fh.write(u"Hello World: ä")
  fh.seek(0)
  for line in fh:
    print line

os.unlink(filename)

Ответ 3

tempfile.TemporaryFile имеет параметр кодирования в Python 3:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tempfile
with tempfile.TemporaryFile(mode='w+', encoding='utf-8') as fh:
  fh.write("Hello World: ä")
  fh.seek(0)
  for line in fh:
    print(line)

Обратите внимание, что теперь вам нужно указать mode = 'w +' вместо стандартного двоичного режима. Также обратите внимание, что строковые литералы неявно Unicode в Python 3, там нет модификатора u.

Если вы застряли с Python 2.6, временные файлы всегда являются двоичными, и вам нужно закодировать строку Unicode перед тем, как записать ее в файл:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import tempfile
with tempfile.TemporaryFile() as fh:
  fh.write(u"Hello World: ä".encode('utf-8'))
  fh.seek(0)
  for line in fh:
    print line.decode('utf-8')

Unicode указывает набор символов, а не кодировку, поэтому в любом случае вам нужен способ указать, как кодировать символы Unicode!

Ответ 4

Так как я работаю над программой Python с объектами TemporaryFile, которые должны запускаться как в Python 2, так и в Python 3, я не считаю это удобным для кодирования всех строк, написанных как UTF-8, как и другие ответы.

Вместо этого я написал следующий небольшой polyfill (потому что я не мог найти что-то вроде этого в шесть), чтобы обернуть двоичный файл-подобный объект в файл типа UTF-8:

from __future__ import unicode_literals
import sys
import codecs
if sys.hexversion < 0x03000000:
    def uwriter(fp):
        return codecs.getwriter('utf-8')(fp)
else:
    def uwriter(fp):
        return fp

Используется следующим образом:

# encoding: utf-8
from tempfile import NamedTemporaryFile
with uwriter(NamedTemporaryFile(suffix='.txt', mode='w')) as fp:
    fp.write('Hællo wörld!\n')

Ответ 5

Вы пытаетесь записать объект unicode (u"...") во временный файл, где вы должны использовать закодированную строку ("..."). Вам не нужно явно передавать параметр "encode=", потому что вы уже указали кодировку в строке 2 ("# -*- coding: utf-8 -*-"). Просто используйте fh.write("ä") вместо fh.write(u"ä"), и все должно быть в порядке.

Ответ 6

Отбрасывание u заставило ваш код работать для меня:

fh.write("Hello World: ä")

Я предполагаю, что он уже юникод.