Python 2.X: почему я не могу правильно обрабатывать Unicode?

Я некоторое время экспериментировал с Python 2.X и юникодом. Но я достиг точки, когда это не имеет смысла.

Первая проблема:

Некоторый код ясно объяснит, что я имею в виду. Переменная txt предназначена для имитации функции перевода pyqt4. Который возвращает QString.

# -*- coding: utf-8 -*-
from PyQt4 import QtCore
txt = QtCore.QString(u'può essere / sarà / 日本語')
txtUnicode1 = unicode(txt, errors='replace')
txtUnicode2 = unicode(txt)

При печати() двух строк юникода я получаю:

пу� эссере / сар� / ???

può essere / sarà / 日本語

Конечно, я мог бы получить то же самое, используя QString.__str__(), но я хочу понять, поэтому ради аргумента я хотел бы знать:

  • Почему error='replace' заменяет все закодированные символы, когда это должно делаться только в случае ошибок?
  • Есть ли проблема с использованием unicode(QString) для превращения QString в строку unicode, пригодную для отображения?

Вторая проблема:

Я пытаюсь понять кодирование/декодирование.

>>> a = u'può essere / sarà / 日本'
>>> b = a.encode('utf-8')
>>> a
u'pu\xf2 essere / sar\xe0 / \u65e5\u672c'
>>> b
'pu\xc3\xb2 essere / sar\xc3\xa0 / \xe6\x97\xa5\xe6\x9c\xac'
>>> print a
può essere / sarà / 日本
>>> print b
può essere / sarà / 日本
  • Расшифровывает ли print a и b?
  • Кодированный-кодированный UTF-8 должен быть полностью декодирован? Разве я не должен напечатать закодированную строку?
  • В чем разница между закодированной и декодированной строкой Unicode?

person Aki    schedule 08.03.2012    source источник
comment
Можете ли вы просто уточнить, используете ли вы python 2.x или 3.x. Хотя это (в настоящее время) очевидно на основе префикса строки u, это важная информация для этого вопроса, и в будущем префикс u будет добавлен обратно в python3 (pep414)   -  person ironchefpython    schedule 08.03.2012
comment
@Daenyth: Круто, уже скоро (10 марта, 11:45 — 12:15).   -  person Aki    schedule 08.03.2012
comment
К сожалению, консоль (командная строка) под Windows — беспроигрышный вариант для тестирования ввода-вывода Unicode. Это длинная история, чтобы подробно описать все недостатки, с которыми вы столкнетесь, пытаясь print Unicode-строку. Это не специфичная для Python проблема, но она затрудняет экспериментирование с пониманием Unicode. Вы можете попрактиковаться в терминале Linux или веб-интерфейсе, который может надежно работать с Unicode.   -  person bobince    schedule 10.03.2012
comment
@Aki: Видео готово: pyvideo.org/video/948/   -  person Daenyth    schedule 19.03.2012


Ответы (2)


Давайте запустим старый резерв IDLE и посмотрим, сможем ли мы воспроизвести то, что видите вы.

IDLE 1.1.4      
>>> a = u'può essere / sarà / 日本'

Unsupported characters in input
>>> a = u'pu\xf2 essere / sar\xe0 / \u65e5\u672c'
>>> b = a.encode('utf-8')
>>> a
u'pu\xf2 essere / sar\xe0 / \u65e5\u672c'
>>> b
'pu\xc3\xb2 essere / sar\xc3\xa0 / \xe6\x97\xa5\xe6\x9c\xac'
>>> print a
può essere / sarà / 日本
>>> print b
può essere / sarà / 日本

Обратите внимание, что я вижу что-то другое, когда печатаю b. Это связано с тем, что моя оболочка (IDLE) не интерпретирует последовательность байтов как текст UTF-8, а использует кодировку символов моей платформы cp1252.

Давайте просто дважды проверим это.

>>> import sys
>>> sys.stdout.encoding
'cp1252'

Ага, вот почему у меня другое поведение, чем у тебя. Потому что ваша sys.stdout.encoding - это UTF-8. И именно поэтому, несмотря на то, что a и b являются совершенно разными значениями, они отображают одно и то же; ваш терминал интерпретирует байты как UTF-8.

Поэтому вам может быть интересно, можем ли мы преобразовать нашу последовательность символов Юникода a в последовательность байтов, которую можно отобразить в IDLE.

>>> c = a.encode('cp1252') 

Traceback (most recent call last):
  File "<pyshell#19>", line 1, in -toplevel-
    c = a.encode('cp1252') #uses default encoding
  File "C:\Python24\lib\encodings\cp1252.py", line 18, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode characters in position 20-21: character maps to <undefined>

Ответ - нет; cp1252 не поддерживает кодирование китайских символов в виде байтов.

person ironchefpython    schedule 08.03.2012
comment
Итак, вы можете напечатать a, потому что печать python обнаруживает строку UTF-8 и преобразует ее в cp1252? В то время как он просто переводит b как строку, закодированную cp1252? - person Aki; 08.03.2012
comment
Нет, я могу напечатать a, потому что a — это последовательность (16-битных) символов Юникода. Но b — это последовательность байтов, поэтому python необходимо преобразовать ее в символы, и он делает это, используя кодировку платформы по умолчанию, cp1252. - person ironchefpython; 08.03.2012
comment
Моя версия IDLE использует UTF-8. К сожалению, я также не могу повторить тест, потому что он применяет какую-то кодировку к вводу. - person Mark Ransom; 08.03.2012
comment
@MarkRansom обратите внимание, что я не могу вырезать и вставлять глифы в IDLE, но мне пришлось использовать экранированную версию. Посмотрите на мое второе (успешное) задание на a - person ironchefpython; 08.03.2012
comment
Это очень познавательно, вы случайно не знаете что-нибудь о моих первых двух вопросах? Меня очень интересуют подробности! - person Aki; 08.03.2012
comment
Для меня sys.stdin.encoding возвращает «utf-8», и я уверен, что в прошлом вставлял символы Unicode, поэтому я не уверен, что делаю неправильно. Интересно, что я могу назначить вставленный текст обычной (не Unicode) строке и decode в правильную строку Unicode. - person Mark Ransom; 08.03.2012
comment
@ironchefpython: у вас это наоборот. Когда вы используете print mystring со строкой типа unicode, python неявно вызывает mystring.encode(encoding), где encoding — это либо sys.getdefualtencoding(), либо sys.stdout.encoding, в зависимости от цели. Консоль может принимать только байты (тип str), а не абстрактную концепцию последовательности кодовых точек (тип unicode). - person Daenyth; 08.03.2012
comment
@Daenyth Я уточнил свой ответ, чтобы указать, что я использую не консоль, а оболочку IDLE, основанную на tkinter, которая принимает как байты, так и строки Unicode. - person ironchefpython; 09.03.2012

Во-первых, я предлагаю вам прочитать прекрасную статью Джоэла Сполски, Абсолютный минимум, который обязательно должен знать каждый разработчик программного обеспечения. О Unicode и наборах символов. Это не о Python, но должно помочь вам понять, что происходит.

Различие между строками Unicode и обычными строками становится более четким в Python 3, но это справедливо и для Python 2. Строка Unicode содержит представление строки в Unicode, обычная строка — это строка байтов, содержащая кодировку этого представления Unicode. Документация для unicode объясняет, что происходит. При передаче параметра errors параметр unicode действует иначе, чем когда он не передается и пытается декодировать строку. Непонятно, какую кодировку он пытается декодировать, но он может подумать, что это utf-8, хотя на самом деле это utf-16 или что-то подобное.

Оператор print кодирует строку Юникода в любую кодировку, используемую вашим терминалом. Это может быть ascii, или это может быть utf-8 или что-то еще. print a на самом деле делает print a.encode('utf-8') за кулисами.

Я не ответил на все ваши вопросы, но статья, на которую я ссылаюсь, должна ответить на большинство из них. Надеюсь это поможет!

person Andrew Wilkinson    schedule 08.03.2012