Юникод (UTF-8) чтение и запись в файлы на Python - PullRequest
289 голосов
/ 29 января 2009

У меня какая-то мозговая ошибка в понимании чтения и записи текста в файл (Python 2.4).

# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)

(«u'Capit \ xe1n», «Capit \ xc3 \ xa1n»)

print ss, ss8
print >> open('f1','w'), ss8

>>> file('f1').read()
'Capit\xc3\xa1n\n'

Поэтому я набираю Capit\xc3\xa1n в моем любимом редакторе, в файле f2.

Тогда:

>>> open('f1').read()
'Capit\xc3\xa1n\n'
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
>>> open('f1').read().decode('utf8')
u'Capit\xe1n\n'
>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n\n'

Что я не понимаю здесь? Ясно, что я пропускаю какое-то жизненно важное волшебство (или здравый смысл). Что вводить в текстовые файлы, чтобы получить правильные преобразования?

Что я действительно не могу понять, так это смысл представления UTF-8, если вы не можете заставить Python распознавать его, когда оно приходит извне. Может быть, я должен просто JSON вывести строку и использовать ее вместо этого, так как это имеет превосходное представление! Более того, существует ли ASCII-представление этого объекта Unicode, которое Python будет распознавать и декодировать при входе из файла? Если так, как я могу получить это?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'

Ответы [ 13 ]

659 голосов
/ 10 мая 2009

Вместо того, чтобы связываться с методами кодирования и декодирования, мне проще указать кодировку при открытии файла. Модуль io (добавлен в Python 2.6) предоставляет функцию io.open, которая имеет параметр кодирования.

Используйте метод open из модуля io.

>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

Затем после вызова функции read () возвращается кодированный объект Unicode.

>>>f.read()
u'Capit\xe1l\n\n'

Обратите внимание, что в Python 3 функция io.open является псевдонимом для встроенной функции open. Встроенная функция open поддерживает только аргумент кодирования в Python 3, а не Python 2.

Редактировать: Ранее этот ответ рекомендовал модуль codecs . Модуль кодеков может вызвать проблемы при смешивании read() и readline(), поэтому в этом ответе теперь рекомендуется использовать модуль io .

Используйте метод open из модуля кодеков.

>>>import codecs
>>>f = codecs.open("test", "r", "utf-8")

Затем после вызова функции read () возвращается кодированный объект Unicode.

>>>f.read()
u'Capit\xe1l\n\n'

Если вы знаете кодировку файла, использование пакета кодеков будет намного менее запутанным.

См. http://docs.python.org/library/codecs.html#codecs.open

99 голосов
/ 29 января 2009

В обозначениях

u'Capit\xe1n\n'

"\ xe1" представляет только один байт. «\ x» говорит вам, что «e1» в шестнадцатеричном формате. Когда ты пишешь

Capit\xc3\xa1n

в ваш файл у вас есть "\ xc3". Это 4 байта, и в вашем коде вы все их читаете. Вы можете увидеть это, когда отобразите их:

>>> open('f2').read()
'Capit\\xc3\\xa1n\n'

Вы можете видеть, что обратная косая черта экранирована обратной косой чертой. Итак, в вашей строке четыре байта: "\", "x", "c" и "3".

Edit:

Как и другие отмечали в своих ответах, вы должны просто ввести символы в редакторе, а ваш редактор должен обработать преобразование в UTF-8 и сохранить его.

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

In [15]: print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán

Результатом является строка, закодированная в UTF-8, где символ с акцентом представлен двумя байтами, которые были записаны \\xc3\\xa1 в исходной строке. Если вы хотите получить строку в кодировке Unicode, вы должны снова декодировать с помощью UTF-8.

К вашему редактированию: в вашем файле нет UTF-8. Чтобы действительно увидеть, как это будет выглядеть:

s = u'Capit\xe1n\n'
sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

Сравните содержимое файла utf-8.out с содержимым файла, который вы сохранили в редакторе.

31 голосов
/ 10 февраля 2016

Теперь все, что вам нужно в Python3, это open(Filename, 'r', encoding='utf-8')

[Изменить в 2016-02-10 для запрашиваемого разъяснения]

Python3 добавил параметр encoding в свою функцию открытия. Следующая информация о функции открытия собрана отсюда: https://docs.python.org/3/library/functions.html#open

open(file, mode='r', buffering=-1, 
      encoding=None, errors=None, newline=None, 
      closefd=True, opener=None)

Кодировка - это имя кодировки, используемой для декодирования или кодирования файл. Это следует использовать только в текстовом режиме. Кодировка по умолчанию зависит от платформы (независимо locale.getpreferredencoding () возвращается), но можно использовать любую текстовую кодировку , поддерживаемую Python. См. Список поддерживаемых кодировок в модуле codecs .

Таким образом, добавляя encoding='utf-8' в качестве параметра к функции open, чтение и запись файлов выполняются как utf8 (который теперь также является кодировкой по умолчанию для всего, что делается в Python.)

17 голосов
/ 29 января 2009

Итак, я нашел решение для того, что я ищу, а именно:

print open('f2').read().decode('string-escape').decode("utf-8")

Есть несколько необычных кодеков, которые здесь полезны. Это конкретное чтение позволяет взять представления UTF-8 из Python, скопировать их в файл ASCII и сделать так, чтобы их считывали в Unicode. При декодировании "string-escape" косые черты не удваиваются.

Это допускает ту поездку туда и обратно, которую я представлял.

14 голосов
/ 19 августа 2014

На самом деле, это помогло мне прочитать файл с кодировкой UTF-8 в Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)
14 голосов
/ 09 февраля 2012
# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location = "jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()
6 голосов
/ 21 июня 2017

кроме codecs.open(), можно использовать io.open() для работы с Python2 или Python3 для чтения / записи файла Unicode

пример

import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='\n') as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='\n') as fin:
    text2 = fin.read()

assert text == text2
6 голосов
/ 18 сентября 2014

Чтобы прочитать строку Юникода и затем отправить в HTML, я сделал это:

fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

Полезно для http-серверов с питоном.

6 голосов
/ 29 января 2009

Вы наткнулись на общую проблему с кодировками: как узнать, в какой кодировке находится файл?

Ответ: Вы не можете , если формат файла не предусматривает это. Например, XML начинается с:

<?xml encoding="utf-8"?>

Этот заголовок был тщательно выбран, чтобы его можно было прочитать независимо от кодировки. В вашем случае такой подсказки нет, поэтому ни ваш редактор, ни Python не имеют ни малейшего представления о том, что происходит. Поэтому вы должны использовать модуль codecs и использовать codecs.open(path,mode,encoding), который обеспечивает отсутствующий бит в Python.

Что касается вашего редактора, вы должны проверить, предлагает ли он какой-либо способ установить кодировку файла.

Суть UTF-8 в том, чтобы иметь возможность кодировать 21-битные символы (Unicode) как 8-битный поток данных (потому что это единственное, что могут обрабатывать все компьютеры в мире). Но поскольку большинство ОС предшествуют эпохе Юникода, у них нет подходящих инструментов для прикрепления информации о кодировке к файлам на жестком диске.

Следующая проблема - представление в Python. Это прекрасно объясняется в комментарии heikogerlach . Вы должны понимать, что ваша консоль может отображать только ASCII. Чтобы отобразить Unicode или что-нибудь> = charcode 128, он должен использовать некоторые средства экранирования. В вашем редакторе вы не должны вводить экранированную строку отображения, но что означает эта строка (в этом случае вы должны ввести умлаут и сохранить файл).

Тем не менее, вы можете использовать функцию Python eval (), чтобы превратить экранированную строку в строку:

>>> x = eval("'Capit\\xc3\\xa1n\\n'")
>>> x
'Capit\xc3\xa1n\n'
>>> x[5]
'\xc3'
>>> len(x[5])
1

Как видите, строка "\ xc3" превращена в один символ. Теперь это 8-битная строка в кодировке UTF-8. Чтобы получить Unicode:

>>> x.decode('utf-8')
u'Capit\xe1n\n'

Грегг Линд спросил: Я думаю, что здесь не хватает некоторых частей: файл f2 содержит: hex:

0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

codecs.open('f2','rb', 'utf-8'), например, читает их все в отдельных символах (ожидается) Есть ли способ записи в файл в ASCII, который будет работать?

Ответ: Это зависит от того, что вы имеете в виду. ASCII не может представлять символы> 127. Таким образом, вам нужно как-то сказать «следующие несколько символов означают что-то особенное», что и делает последовательность «\ x». Он гласит: следующие два символа - это код одного символа. «\ u» делает то же самое, используя четыре символа для кодирования Unicode до 0xFFFF (65535).

Таким образом, вы не можете напрямую записывать Unicode в ASCII (потому что ASCII просто не содержит одинаковых символов). Вы можете записать его как экранирование строки (как в f2); в этом случае файл может быть представлен как ASCII. Или вы можете записать его как UTF-8, в этом случае вам нужен 8-битный безопасный поток.

Ваше решение, использующее decode('string-escape'), работает, но вы должны знать, сколько памяти вы используете: в три раза больше, чем codecs.open().

Помните, что файл - это просто последовательность байтов с 8 битами. Ни биты, ни байты не имеют значения. Это ты говоришь "65 означает" А "". Так как \xc3\xa1 должно стать «а», но компьютер не имеет средств для этого, вы должны указать это, указав кодировку, которая использовалась при записи файла.

5 голосов
/ 29 января 2009

Ваш любимый текстовый редактор не понимает, что \xc3\xa1 должны быть символьными литералами, но интерпретирует их как текст. Вот почему вы получаете двойную обратную косую черту в последней строке - теперь это настоящая обратная косая черта + xc3 и т. Д. В вашем файле.

Если вы хотите читать и записывать закодированные файлы в Python, лучше всего использовать модуль codecs .

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

>>> s = file("f1").read()
>>> print unicode(s, "Latin-1")
Capitán

Затем вставьте эту строку в ваш редактор и убедитесь, что он хранит ее, используя Latin-1. В предположении, что буфер обмена не искажает строку, должен работать круговой поток.

...