Как упоминалось выше, объекты файлов, открытые в текстовом режиме, при передаче данных между программой и внешним файлом всегда преобразуют данные в соответствии с кодировкой по умолчанию или указанной явно. При записи в файл выполняется кодирование данных, а при чтении — декодирование. Для файлов, открытых в двоичном режиме, никаких преобразований не выполняется, потому что именно это и требуется для действительно двоичных данных. Например, взгляните на следующую строку, содержащую символ Юникода, двоичное представление которого выходит за рамки 7-битового диапазона представления символов ASCII:
>>> data = ‘sp\xe4m’
>>> data
‘spam’
>>> 0xe4, bin(0xe4), chr(0xe4)
(228, ‘0b11100100’, ‘a’)
Эту строку можно закодировать вручную в соответствии с той или иной кодировкой, и для разных кодировок будут получаться различные двоичные представления строки:
>>> data.encode(‘latin1’) b’sp\xe4m’ |
# 8-битовые символы: ascii + дополнительные |
>>> data.encode(‘utf8’) b’sp\xc3\xa4m’ |
# 2 байта отводится только # для специальных символов |
>>> data.encode(‘ascii’) UnicodeEncodeError: ‘ascii’ ordinal not in range(128) |
# кодирование в ascii невозможно codec can’t encode character ‘\xe4’ in position 2: |
(UnicodeEncodeError: кодек ‘ascii’ не может преобразовать символ ‘\xe4’ в позиции 2: число выходит за пределы range(128) )
Интерпретатор Python отображает печатаемые символы в таких строках как обычно, а непечатаемые — в виде шестнадцатеричных экранированных значений \xNN, количество которых увеличивается при использовании некоторых более сложных схем кодирования (cp500 в следующем примере — это кодировка EBCDIC):
> >> data.encode(‘utf16’) # по 2 байта на символ плюс преамбула b’\xff\xfes\x00p\x00\xe4\x00m\x00’
> >> data.encode(‘cp500’) # кодировка ebcdic: двоичное представление b’\xa2\x97C\x94’ # строки существенно отличается
Результат кодирования здесь отражает двоичное представление строки, которое будет записано в файл при сохранении. Однако выполнять кодирование вручную обычно не требуется, потому что для текстовых файлов кодирование выполняется автоматически при передаче данных — данные декодируются при чтении и кодируются при записи, в соответствии с именем указанной кодировки (или с использованием кодировки, используемой на текущей платформе: смотрите описание функции sys.getdefaultencoding). Продолжим интерактивный сеанс:
> >> open(‘data.txt’, ‘w’, encoding=’latin1’).write(data) 4
>>> open(‘data.txt’, ‘r’, encoding=’latin1’).read()
‘spam’
>>> open(‘data.txt’, ‘rb’).read()
b’sp\xe4m’
Если файл открыть в двоичном режиме, никаких преобразований производиться не будет — последняя инструкция в предыдущем примере предъявляет в точности то, что хранится в файле. Чтобы увидеть отличия при использовании других кодировок, сохраним эту строку еще раз:
>>> open(‘data.txt’, ‘w’, encoding=’utf8’).write(data) # кодировка utf8
4
>>> open(‘data.txt’, ‘r’, encoding=’utf8’).read() # декодирование: отменяет
‘spam’ # кодирование
>>> open(‘data.txt’, ‘rb’).read() # преобразование
b’sp\xc3\xa4m’ # не производится
На этот раз двоичное содержимое файла получилось другим, но в результате автоматического декодирования, которое выполняется при чтении файла в текстовом режиме, возвращается та же самая строка. В действительности, кодировка имеет значение для строк, только когда они находятся в файлах, — сразу после загрузки в память строки превращаются в простые последовательности символов Юникода («кодовые пункты»). Этот этап преобразования желателен для текстовых файлов, но не для двоичных. При использовании двоичных режимов этап преобразования содержимого пропускается, поэтому при работе с истинно двоичными данными необходимо использовать эти режимы. Если вам нужны доказательства, попробуйте сами: попытка записать или прочитать недекодируемые данные в текстовом режиме приведет к появлению ошибки:
>>> open(‘data.txt’, ‘w’, encoding=’ascii’).write(data)
UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘\xe4’ in position 2: ordinal not in range(128)
(UnicodeEncodeError: кодек ‘ascii’ не может преобразовать символ ‘\xe4’ в позиции 2: число выходит за пределы range(128) )
>>> open(r’C:\Python31\python.exe’, ‘r’).read()
UnicodeDecodeError: ‘charmap’ codec can’t decode byte 0x90 in position 2: character maps to <undefined>
(UnicodeDecodeError: кодек ‘charmap’ не может преобразовать байт 0x90 в позиции 2: символ отображается в символ <undefined> )
Двоичный режим можно также рассматривать, как последний шанс прочитать текстовый файл, если он не может быть декодирован с использованием кодировки по умолчанию, а кодировка файла неизвестна. Следующий программный код воссоздает оригинальные строки, когда кодировка известна, но терпит неудачу, когда она неизвестна, если только не использовать двоичный режим (такие ошибки могут возникать как при чтении данных, так и при записи, но в любом случае программный код терпит неудачу):
>>> open(‘data.txt’, ‘w’, encoding=’cp500’).writelines([‘spam\n’, ‘ham\n’])
>>> open(‘data.txt’, ‘r’, encoding=’cp500’).readlines()
[‘spam\n’, ‘ham\n’]
>>> open(‘data.txt’, ‘r’).readlines()
UnicodeDecodeError: ‘charmap’ codec can’t decode byte 0x81 in position 2: character maps to <undefined>
(UnicodeDecodeError: кодек ‘charmap’ не может преобразовать байт 0x81 в позиции 2: символ отображается в символ <undefined> )
>>> open(‘data.txt’, ‘rb’).readlines() [b’\xa2\x97\x81\x94\r%\x88\x81\x94\r%’]
>>> open(‘data.txt’, ‘rb’).read()
b’\xa2\x97\x81\x94\r%\x88\x81\x94\r%’
Если вы имеете дело только с текстом ASCII, вы можете пропустить все, что связано с кодировками, — данные в файлах будут один-в-один отображаться в символы в строках, потому что ASCII является подмножеством большинства кодировок, используемых по умолчанию. Если вам приходится обрабатывать файлы, созданные с применением других кодировок, и, возможно, на других платформах (например, файлы, полученные из Интернета), вам может потребоваться использовать двоичный режим, если кодировка заранее не известна. Однако имейте в виду, что текст в кодированном двоичном представлении не может обрабатываться так, как вам хотелось бы: текст, закодированный с применением определенной кодировки, не может сравниваться или объединяться с текстом, закодированным с применением других кодировок.
И снова за дополнительной информацией о Юникоде обращайтесь к другим ресурсам. Мы еще не раз будем возвращаться к теме Юникода в этой книге: в главе 9 будет показано, какое влияние оказывает Юникод на виджет Text из библиотеки tkinter, а в части IV, охватывающей вопросы программирования для Интернета, мы узнаем, как это отражается на данных, доставляемых по сети с использованием протоколов FTP, электронной почты и в Интернете в целом. Текстовые файлы обладают еще одной особенностью, отсутствующей у двоичных файлов: преобразование символов конца строки, что является темой следующего раздела.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011