понедельник, 24 декабря 2012 г.

Преобразования между кодировками в Python

 Строковые литералы определяются с помощью одинарных,  двойных или тройных кавычек.
Добавление символа b или B перед открывающей кавычкой в любой из этих форм приводит к созданию объекта типа bytes.


>>> B = b'solaris'

>>> type(B), type(S)
( < class 'bytes' >,  < class 'str' > )
>>>

>>> B
b'solaris'
 

>>> S
'oracle'
>>>

В действительности тип byte - это последовательность целых чисел.

 >>> B[0], S[0]
(115, 'o')

>>> B[1:], S[1:]
(b'olaris', 'racle')

>>> list(B), list(S)
([115, 111, 108, 97, 114, 105, 115], ['o', 'r', 'a', 'c', 'l', 'e'])
>>>

Типы byte и srt неизменяемые:

>>> B[0] = 'Z'
Traceback (most recent call last):
  File "", line 1, in
    B[0] = 'Z'
TypeError: 'bytes' object does not support item assignment
>>>
>>> S[0] = 'Z'
Traceback (most recent call last):
  File "", line 1, in
    S[0] = 'Z'
TypeError: 'str' object does not support item assignment
>>>


Кодирование строк символов ASCII:

>>> ord('o')
111

>>> chr(111)
'o'

>>> S = 'oracle'

>>> S
'oracle'

>>> len(S)
6

>>> [ord(i) for i in S]
[111, 114, 97, 99, 108, 101]

>>> S.encode('ascii')
b'oracle'

>>> S.encode('latin-1')
b'oracle'

>>> S.encode('utf-8')
b'oracle'

>>> S.encode('latin-1')[0]
111

>>> list(S.encode('latin-1'))
[111, 114, 97, 99, 108, 101]
>>>

Кодирование строк символов не ASCII:

Для представления символов можно использовать экранированные последовательности значений байтов и символов Unicode.

Шестнадцатеричные значения  0xdc  и 0xd6  представляют коды двух специальных символов, не входящих в диапазон 7-битных символов ASCII:

>>> chr(0xdc)
'Ü'

>>> chr(0xd6)
'Ö'

>>> S = '\xdc\xd6'
>>> S
'ÜÖ'

>>> S = '\u00dc\u00d6'
>>> S
'ÜÖ'

>>> S = chr(0xdc) + chr(0xd6)
>>> S
'ÜÖ'

>>> len(S)
2                              # это 2 символа (не число байтов)
>>>

Если попробовать закодировать строки символов не ASCII в последовательность простых байтов, используя кодировку ASCII, мы получим сообщение об ошибке.

Однако, если указать кодировку latin-1, ошибки не будет и каждому символу в строке будет поставлен в соответствие отдельный байт.

При использовании UTF-8 для каждого символа будет выделено по  2 - байта.

>>> S = '\u00dc\u00d6'
>>> S
'ÜÖ'

>>> len(S)
2

>>> S.encode('ascii')
Traceback (most recent call last):
  File "", line 1, in
    S.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

>>> S.encode('latin-1')
b'\xdc\xd6'

>>> S.encode('utf-8')
b'\xc3\x9c\xc3\x96'

>>> len(S.encode('latin-1'))
2                    
#  2 байта

>>> len(S.encode('utf-8'))
4                    
#  4 байта
>>>

Можно пойти обратным путем - прочитать последовательность байтов из файла и декодировать их в сторону символов Unicode.

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

>>> B = b'\xdc\xd6'
>>> B
b'\xdc\xd6'

>>> len(B)
2

>>> B.decode('latin-1')
'ÜÖ'

>>> B = b'\xc3\x9c\xc3\x96'

>>> len(B)
4

>>> B.decode('utf-8')
'ÜÖ'

>>> len(B.decode('utf-8'))
2
>>>

Другие способы кодирования строк в Unicode:

>>> S = 'A\u00dcB\U000000d6C'
>>> S
'AÜBÖC'

>>> len(S)
5                #  5 символов

>>> S.encode('latin-1')
b'A\xdcB\xd6C'

>>> len(S.encode('latin-1'))
5               #  5 байтов

>>> S.encode('utf-8')
b'A\xc3\x9cB\xc3\x96C'

>>> len(S.encode('utf-8'))
7               #  7 байтов
>>>

Некоторые кодировки могут иметь существенное различие в кодах символов.
Например кодировка cp500 (EBCDIC), даже символы ASCII кодирует не так как некоторые другие кодировки:

>>> S
'AÜBÖC'

>>> S.encode('cp500')
b'\xc1\xfc\xc2\xec\xc3'

>>> S.encode('cp850')
b'A\x9aB\x99C'

>>> S = 'oracle'

>>> S.encode('latin-1')
b'oracle'

>>> S.encode('utf-8')
b'oracle'

>>> S.encode('cp500')
b'\x96\x99\x81\x83\x93\x85'

>>> S.encode('cp850')
b'oracle'
>>>

С технической точки зрения, можно составлять строки Unicode по частям, используя функцию chr() вместо экранированных шестнадцатеричных значений, но это может оказаться утомительным в случае длинных строк:

>>> S = 'A' + chr(0xdc) + 'B' + chr(0xd6) + 'C'
>>> S
'AÜBÖC'
>>>

  В Python допускается в строках типа str кодировать специальные символы с использованием шестнадцатеричных экранированных последовательностей значений байтов и символов Unicode.
 Но в строках типа bytes могут применяться только шестнадцатеричные экранированные последовательности значений байтов.

  Экранированные последовательности значений символов Unicode в строках типа bytes будут интерпретироваться буквально, а не как экранированные последовательности.
  Фактически строки bytes должны декодироваться в строки str, чтобы корректно вывести символы, не являющиеся символами ASCII.

>>> S = 'A\xdcB\xd6C'
>>> S
'AÜBÖC'

>>> S = 'A\u00dcB\U000000d6C'
>>> S
'AÜBÖC'                        # распознает значения символов

>>> B = b'A\xdcB\xd6C'
>>> B
b'A\xdcB\xd6C'              # распознает последовательности байтов

>>> B = b'A\u00dcB\U000000d6C'
>>> B
b'A\\u00dcB\\U000000d6C'     # буквально интерпретируются
>>>


>>> B = b'A\xdcB\xd6C'
>>> B
b'A\xdcB\xd6C'

>>> print(B)
b'A\xdcB\xd6C'

>> B.decode('latin-1')
'AÜBÖC'
>>>

  При определении литералов bytes допускается использовать символы ASCII, а для байтов со значениями выше  127 - экранированные последовательности шестнадцатеричных значений.

  В литералах str допускается использовать любые символы, имеющиеся в исходной кодировке.
( в качестве которой, по умолчанию используется UTF-8, если в исходном файле явно не была объявлена другая кодировка.)

>>> S = 'AÜBÖC'
>>> S
'AÜBÖC'

>>> B = b'A\xdcB\xd6C'
>>> B
b'A\xdcB\xd6C'

>>> B.decode('latin-1')
'AÜBÖC'

>>> S.encode()
b'A\xc3\x9cB\xc3\x96C'            # системная кодировка UTF-8

>>> S.encode('UTF-8')
b'A\xc3\x9cB\xc3\x96C'

>>> B.decode()                                      # простые байты не соответствуют кодировке UTF-8
Traceback (most recent call last):
  File "", line 1, in
    B.decode()
UnicodeDecodeError: 'utf8' codec can't decode byte 0xdc in position 1: invalid continuation byte
>>>  


  Преобразования между кодировками:

>>> S = 'AÜBÖC'
>>> S
'AÜBÖC'

>>> S.encode()
b'A\xc3\x9cB\xc3\x96C'

>>> T = S.encode('cp500')
>>> T
b'\xc1\xfc\xc2\xec\xc3'

>>> U = T.decode('cp500')
>>> U
'AÜBÖC'

>>> U.encode()
b'A\xc3\x9cB\xc3\x96C'
>>>