Юникод (UTF-8) чтение и запись в файлы на Python

331

У меня какой-то мозговой сбой в понимании чтения и записи текста в файл (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'
Грегг Линд
источник

Ответы:

110

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

u'Capit\xe1n\n'

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

Capit\xc3\xa1n

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

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

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

Редактировать:

Как отмечали другие в своих ответах, вам просто нужно ввести символы в редакторе, а ваш редактор должен затем обработать преобразование в 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с содержимым файла, который вы сохранили в редакторе.


источник
Итак, какой смысл в кодированном формате utf-8, если python может читать файлы, используя его? Другими словами, есть ли в ascii представление, которое python будет читать в \ xc3 как 1 байт?
Грегг Линд
4
Ответ на ваш вопрос «Так в чем смысл…» - «Му». (поскольку Python может читать файлы, закодированные в UTF-8). На ваш второй вопрос: \ xc3 не является частью набора ASCII. Возможно, вы имеете в виду «8-битное кодирование». Вы запутались в Юникоде и кодировках; все в порядке, многие
tzot
8
Попробуйте прочитать это как учебник: joelonsoftware.com/articles/Unicode.html
tzot
примечание: u'\xe1'это одна кодовая точка Unicode, U+00e1которая может быть представлена ​​с использованием 1 или более байтов в зависимости от кодировки символов (в utf-8 это 2 байта). b'\xe1'один байт (число 225), какая буква, если таковая имеется, зависит от кодировки символов, используемой для его декодирования, например, это б( U+0431) в cp1251, с( U+0441) в cp866 и т. д.
jfs
11
Удивительно, сколько британских кодировщиков говорят «просто используй ascii», а затем не понимают, что знак £ не тот. Большинство не знают, что ascii! = Локальная кодовая страница (например, latin1).
Дэнни Стейпл
713

Вместо того, чтобы связываться с методами кодирования и декодирования, мне проще указать кодировку при открытии файла. 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.

Изменить: Ранее этот ответ рекомендовал модуль кодеков . Модуль кодеков может вызвать проблемы при микшировании 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.

Тим Сваст
источник
74
Прекрасно работает и для записи файлов, вместо того, open(file,'w')чтобы codecs.open(file,'w','utf-8')решить
Мэтт Коннолли,
1
Это ответ, который я искал :)
Джастин
6
Имеет ли codecs.open(...)метод также полностью соответствовать with open(...):стилю, где withзаботится о закрытии файла после все сделано? Кажется, все равно работает.
попробуй поймай наконец
2
@ try-catch-finally Да. Я использую with codecs.open(...) as f:все время.
Тим Суаст
6
Я бы хотел, чтобы я проголосовал за это сто раз. После нескольких дней мучений из-за проблем с кодировкой, вызванных большим количеством смешанных данных, и многословного прочтения о кодировании, этот ответ похож на воду в пустыне. Жаль, что я видел это раньше.
Майк Жирар
46

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

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

Python3 добавил параметр кодирования в свою функцию open. Следующая информация об открытой функции собрана отсюда: 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. См. Модуль кодеков для списка поддерживаемых кодировок.

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

Dakusan
источник
Не могли бы вы более подробно изложить свой ответ, добавив немного больше описания предлагаемого вами решения?
abarisone
2
Похоже, это доступно в Python 2 с использованием модуля кодеков - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Тейлор Эдмистон
18

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

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

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

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

Грегг Линд
источник
1
Хороший ответ, я протестировал оба решения (codecs.open(file,"r","utf-8")и просто, open(file,"r").read().decode("utf-8")и оба работали отлично.
Eagle
Я получаю объект "TypeError: ожидаемый объект str, bytes или os.PathLike, а не _io.TextIOWrapper", почему?
JinSnow
Я думаю , что , учитывая количество upvotes, это будет отличная идея , чтобы принять второй ответ :)
Жако
14
# -*- 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()
Рикардо
источник
14

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

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)
Сина
источник
6

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

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

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

praj
источник
6

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

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

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

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

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

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

Следующая проблема - представление в 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должен стать «à», но компьютер не имеет средств для этого, вы должны указать это, указав кодировку, которая использовалась при записи файла.

Аарон Дигулла
источник
Я думаю, что здесь не хватает некоторых частей: файл f2 содержит: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. Например, codecs.open ('f2', 'rb', 'utf-8') читает их все в отдельных символах (ожидается). Есть ли способ записи в файл в ascii, который будет работать?
Грегг Линд
6

кроме того 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
Райан
источник
Да, лучше использовать io; Но я написал с утверждением , как это with io.open('data.txt', 'w', 'utf-8') as file:и есть ошибка: TypeError: an integer is required. После того как я перешел на with io.open('data.txt', 'w', encoding='utf-8') as file:и это сработало.
Эван Ху
5

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

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

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

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

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

Торстен Марек
источник
4

Последовательность \ x .. специфична для Python. Это не универсальная escape-последовательность байтов.

То, как вы на самом деле вводите кодировку в кодировке UTF-8, не являющуюся ASCII, зависит от вашей ОС и / или вашего редактора. Вот как вы делаете это в Windows . Для OS X , чтобы войти с острым ударением вы можете просто нажать + , затем , и почти все текстовые редакторы в OS X поддержка UTF-8.optionEA

ʞɔıu
источник
3

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

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')
hipertracker
источник
1

Я пытался разобрать iCal с помощью Python 2.7.9:

из календаря импорта icalendar

Но я получаю:

 Traceback (most recent call last):
 File "ical.py", line 92, in parse
    print "{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

и это было исправлено просто:

print "{}".format(e[attr].encode("utf-8"))

(Теперь он может напечатать liké á böss.)

Алекс Рош
источник
0

Я нашел самый простой подход, изменив кодировку по умолчанию всего сценария на «UTF-8»:

import sys
reload(sys)
sys.setdefaultencoding('utf8')

любое open, printили другое утверждение будет просто использовать utf8.

Работает по крайней мере для Python 2.7.9.

Thx идет в https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( посмотри в конец).

dr0i
источник