Чтение и запись данных Unicode
После того как вы написали код,работающий с данными Unicode,следующей проблемой становится ввод/вывод.Как получить строки Unicode в вашей программе,и как преобразовать Unicode в форму,пригодную для хранения или передачи?
Возможно,что вам не придется ничего делать в зависимости от ваших источников входных и выходных данных;вам следует проверить,поддерживают ли библиотеки,используемые в вашем приложении,Unicode нативно.Например,парсеры XML часто возвращают данные в формате Unicode.Многие реляционные базы данных также поддерживают столбцы с значениями Unicode и могут возвращать значения Unicode из SQL-запросов.
Данные Unicode обычно преобразуются в определенную кодировку, прежде чем они будут записаны на диск или отправлены через сокет. Всю работу можно сделать самостоятельно: открыть файл, прочитать из него 8-битный байтовый объект и преобразовать байты с помощью bytes.decode(encoding) . Однако ручной подход не рекомендуется.
Одна из проблем заключается в многобайтовой природе кодировок;один символ Unicode может быть представлен несколькими байтами.Если вы хотите читать файл кусками произвольного размера (скажем,1024 или 4096 байт),вам нужно написать код обработки ошибок,чтобы поймать случай,когда в конце куска считывается только часть байтов,кодирующих один символ Unicode.Одним из решений может быть чтение всего файла в память,а затем выполнение декодирования,но это не позволит вам работать с файлами очень большого размера;если вам нужно прочитать файл размером 2 Гб,вам потребуется 2 Гб оперативной памяти.(На самом деле больше,так как по крайней мере на мгновение вам потребуется держать в памяти и кодированную строку,и ее версию в Unicode).
Решением было бы использование низкоуровневого интерфейса декодирования, чтобы поймать случай неполных последовательностей кодирования. Работа по реализации этого уже сделана за вас: встроенная функция open() может возвращать файлоподобный объект, который предполагает, что содержимое файла находится в указанной кодировке, и принимает параметры Unicode для таких методов, как read() и write() . Это работает через параметры кодирования и ошибок open() , которые интерпретируются точно так же, как в str.encode() и bytes.decode() .
Поэтому чтение Юникода из файла очень просто:
with open('unicode.txt', encoding='utf-8') as f: for line in f: print(repr(line))
Также можно открывать файлы в режиме обновления,позволяющем как чтение,так и запись:
with open('test', encoding='utf-8', mode='w+') as f: f.write('\u4500 blah blah blah\n') f.seek(0) print(repr(f.readline()[:1]))
Символ Unicode U+FEFF используется в качестве метки порядка байтов (BOM) и часто записывается как первый символ файла, чтобы облегчить автоматическое определение порядка байтов в файле. Некоторые кодировки, такие как UTF-16, предполагают наличие спецификации в начале файла; при использовании такой кодировки спецификация будет автоматически записана как первый символ и будет автоматически удалена при чтении файла. Существуют варианты этих кодировок, такие как «utf-16-le» и «utf-16-be» для кодировок с прямым и обратным порядком байтов, которые определяют один конкретный порядок байтов и не пропускают спецификацию.
В некоторых областях также принято использовать «BOM» в начале файлов с кодировкой UTF-8;это название вводит в заблуждение,поскольку UTF-8 не зависит от порядка байтов.Этот знак просто сообщает,что файл закодирован в UTF-8.Для чтения таких файлов используйте кодек ‘utf-8-sig’,чтобы автоматически пропустить метку,если она присутствует.
Unicode filenames
Большинство широко используемых сегодня операционных систем поддерживают имена файлов, содержащие произвольные символы Unicode. Обычно это реализуется путем преобразования строки Unicode в некоторую кодировку, которая различается в зависимости от системы. Сегодня Python сближается с использованием UTF-8: Python для MacOS использовал UTF-8 в нескольких версиях, а Python 3.6 также переключился на использование UTF-8 в Windows. В системах Unix будет только кодировка файловой системы . если вы установили переменные среды LANG или LC_CTYPE ;если нет, кодировка по умолчанию снова UTF-8.
Функция sys.getfilesystemencoding() возвращает кодировку для использования в вашей текущей системе на тот случай, если вы хотите выполнить кодировку вручную, но особых причин для беспокойства нет. При открытии файла для чтения или записи вы обычно можете просто указать строку Unicode в качестве имени файла, и она будет автоматически преобразована в правильную для вас кодировку:
filename = 'filename\u4500abc' with open(filename, 'w') as f: f.write('blah\n')
Функции в модуле os ,такие как os.stat() , также принимают имена файлов в формате Unicode.
Функция os.listdir() возвращает имена файлов, что вызывает вопрос: должна ли она возвращать версию имен файлов в формате Unicode или должна возвращать байты, содержащие закодированные версии? os.listdir() может делать и то, и другое, в зависимости от того, указали ли вы путь к каталогу в виде байтов или строки Unicode. Если вы передадите строку Unicode в качестве пути, имена файлов будут декодированы с использованием кодировки файловой системы, и будет возвращен список строк Unicode, а передача байтового пути вернет имена файлов в виде байтов. Например, предположив, что файловая система по умолчанию использует кодировку UTF-8, запустив следующую программу:
fn = 'filename\u4500abc' f = open(fn, 'w') f.close() import os print(os.listdir(b'.')) print(os.listdir('.'))
$ python listdir-test.py [b'filename\xe4\x94\x80abc', . ] ['filename\u4500abc', . ]
Первый список содержит имена файлов в кодировке UTF-8,а второй-версии в кодировке Unicode.
Обратите внимание,что в большинстве случаев при использовании этих API следует просто придерживаться Юникода.API байтов следует использовать только в системах,где могут присутствовать недекодируемые имена файлов;в настоящее время это практически только Unix-системы.
Советы по написанию программ с поддержкой Юникода
В этом разделе приведены некоторые рекомендации по написанию программного обеспечения,работающего с Unicode.
Программное обеспечение должно работать со строками Unicode только внутри,декодируя входные данные как можно быстрее и кодируя выходные только в конце.
Если вы попытаетесь написать функции обработки, которые принимают как Unicode, так и байтовые строки, вы обнаружите, что ваша программа уязвима для ошибок, где бы вы ни сочетали два разных типа строк. Нет автоматического кодирования или декодирования: если вы сделаете, например, str + bytes , будет TypeError .
При использовании данных,поступающих из веб-браузера или другого ненадежного источника,распространенным приемом является проверка на наличие недопустимых символов в строке перед использованием строки в сформированной командной строке или сохранением ее в базе данных.Если вы делаете это,будьте осторожны,проверяйте декодированную строку,а не закодированные байтовые данные;некоторые кодировки могут обладать интересными свойствами,например,не быть биективными или не быть полностью совместимыми с ASCII.Это особенно актуально,если входные данные также указывают кодировку,поскольку в этом случае злоумышленник может выбрать хитрый способ спрятать вредоносный текст в закодированном байтовом потоке.
Преобразование между кодировками файлов
Класс StreamRecoder может прозрачно преобразовывать между кодировками, принимая поток, возвращающий данные в кодировке №1, и ведя себя как поток, возвращающий данные в кодировке №2.
Например, если у вас есть входной файл f на языке Latin-1, вы можете обернуть его с помощью StreamRecoder , чтобы вернуть байты, закодированные в UTF-8:
new_f = codecs.StreamRecoder(f, # en / decoder: используется read () для кодирования результатов и # с помощью write () для декодирования его ввода. codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'), # reader / writer: используется для чтения и записи в поток. codecs.getreader('latin-1'), codecs.getwriter('latin-1') )
Файлы в неизвестной кодировке
Что вы можете сделать, если вам нужно внести изменения в файл, но вы не знаете кодировку файла? Если вы знаете, что кодировка совместима с ASCII, и хотите проверить или изменить только части ASCII, вы можете открыть файл с помощью обработчика ошибок surrogateescape :
with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f: data = f.read() # внести изменения в строку 'data' with open(fname + '.new', 'w', encoding="ascii", errors="surrogateescape") as f: f.write(data)
Обработчик ошибок surrogateescape будет декодировать любые байты, отличные от ASCII, как кодовые точки в специальном диапазоне от U + DC80 до U + DCFF. Эти кодовые точки затем снова превратятся в те же байты, когда обработчик ошибок surrogateescape используется для кодирования данных и их обратной записи.
References
В одном из разделов Mastering Python 3 Input/Output , выступлении Дэвида Бизли на PyCon 2010, обсуждается обработка текста и обработка двоичных данных.
На слайдах в формате PDF для презентации Марка-Андре Лембурга «Написание приложений с поддержкой Unicode на языке Python» обсуждаются вопросы кодировки символов, а также способы интернационализации и локализации приложения. Эти слайды охватывают только Python 2.x.
The Guts of Unicode in Python — это выступление Бенджамина Петерсона на PyCon 2013, в котором обсуждается внутреннее представление Unicode в Python 3.3.
Acknowledgements
Первоначальный проект этого документа был написан Эндрю Кючлингом.Впоследствии он был пересмотрен Александром Белопольским,Георгом Брандлом,Эндрю Кучлингом и Эцио Мелотти.
Спасибо следующим людям,которые заметили ошибки или высказали предложения по этой статье:Éric Araujo,Nicholas Bastin,Nick Coghlan,Marius Gedminas,Kent Johnson,Ken Krugler,Marc-André Lemburg,Martin von Löwis,Terry J.Reedy,Serhiy Storchaka,Eryk Sun,Chad Whitacre,Graham Wideman.