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

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

Ответ 1

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

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 в исходной строке. Если вы хотите иметь строку юникода, вам нужно снова декодировать UTF-8.

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

# -*- 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()

Ответ 5

Собственно, это работало для меня для чтения файла с кодировкой UTF-8 в Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)

Ответ 6

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

[Редактировать в 2016-02-10 за запрошенное разъяснение]

Python3 добавил параметр кодировки в свою открытую функцию. Ниже приведена следующая информация об открытой функции: 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' в качестве параметра для открытой функции, чтение и запись файлов выполняются как utf8 (который также теперь является кодировкой по умолчанию для всего, что сделано на Python.)

Ответ 7

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

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

Полезно для http-серверов с поддержкой python.

Ответ 8

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

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

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

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

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

Ответ 9

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

Ответ: вы не можете, если для этого используется формат файла. 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(), чтобы превратить escape-строку в строку:

>>> 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 просто не содержит одинаковых символов). Вы можете записать его как строки escape (как в f2); в этом случае файл может быть представлен как ASCII. Или вы можете записать его как UTF-8, и в этом случае вам нужен 8-разрядный безопасный поток.

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

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

Ответ 10

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

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

Ответ 11

кроме 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

Ответ 12

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

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')

Ответ 13

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

из icalendar import Календарь

Но я получал:

 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"))

(Теперь он может печатать как "böss.)