SQLite, python, unicode и не-utf

Я начал с попытки сохранить строки в sqlite с помощью python и получил сообщение:

sqlite3.ProgrammingError: вы должны не используйте 8-битные байты, если вы используйте text_factory, который может интерпретировать 8-битные байты (например text_factory = ул). Настоятельно рекомендуется, чтобы вы вместо этого просто приложение к строкам Unicode.

Хорошо, я переключился на строки Unicode. Затем я начал получать сообщение:

sqlite3.OperationalError: не удалось декодировать в столбец UTF-8 'tag_artist' с текстом 'Sigur Rós'

при попытке извлечь данные из db. Больше исследований, и я начал кодировать его в utf8, но затем "Sigur Rós" начинает выглядеть как "Sigur Rós"

примечание: Моя консоль была установлена ​​для отображения в 'latin_1', как указал @John Machin.

Что дает? После прочтения этого, описывающего точно такую ​​же ситуацию, в которой я нахожусь, кажется, что совет должен игнорировать другой совет и использовать 8-битный bytestrings в конце концов.

Я не знал много о unicode и utf, прежде чем начал этот процесс. За последние пару часов я узнал немало, но я все еще не знаю, есть ли способ правильно преобразовать '-' из латинского-1 в utf-8 и не калечить его. Если этого не произойдет, почему SQLite настоятельно рекомендует переключить мое приложение на строки unicode?


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


Резюме ответов

Позвольте мне сначала изложить цель, насколько я ее понимаю. Целью обработки различных кодировок, если вы пытаетесь преобразовать их между собой, является понимание того, что такое исходная кодировка, а затем преобразовать ее в unicode с использованием этой исходной кодировки, а затем преобразовать ее в нужную кодировку. Unicode является базой, а кодировки - отображением подмножеств этой базы. utf_8 имеет место для каждого символа в юникоде, но поскольку они не находятся на том же месте, что, например, latin_1, строка, закодированная в utf_8 и отправленная в консоль latin_1, не будет выглядеть так, как вы ожидаете. В python процесс перехода на unicode и в другую кодировку выглядит следующим образом:

str.decode('source_encoding').encode('desired_encoding')

или если str уже находится в Юникоде

str.encode('desired_encoding')

Для sqlite я на самом деле не хотел его снова закодировать, я хотел его декодировать и оставить в формате Unicode. Вот четыре вещи, которые вам, возможно, нужно знать, когда вы пытаетесь работать с юникодом и кодировками в python.

  • Кодирование строки, с которой вы хотите работать, и кодировку, которую вы хотите получить.
  • Системное кодирование.
  • Консольная кодировка.
  • Кодирование исходного файла

Разработка:

(1) Когда вы читаете строку из источника, она должна иметь некоторую кодировку, например latin_1 или utf_8. В моем случае я получаю строки из имен файлов, поэтому, к сожалению, я могу получить какую-либо кодировку. Windows XP использует UCS-2 (система Unicode) как свой собственный тип строки, который мне кажется изменчивым. К счастью для меня, символы в большинстве имен файлов не состоят из более чем одного типа кодировки источника, и я думаю, что все мои были либо полностью latin_1, полностью utf_8, либо просто ascii (который является подмножеством обоих те). Поэтому я просто прочитал их и расшифровал, как будто они все еще были в latin_1 или utf_8. Возможно, однако, что вы можете иметь latin_1 и utf_8 и любые другие символы, смешанные вместе в имени файла в Windows. Иногда эти персонажи могут появляться в виде ящиков, иногда они просто выглядят искалеченными, а в других случаях они выглядят правильными (акцентированные персонажи и многое другое). Двигаемся дальше.

(2) Python имеет системную кодировку по умолчанию, которая устанавливается при запуске python и не может быть изменена во время выполнения. Подробнее см. здесь. Грязное резюме... ну вот файл, который я добавил:

\# sitecustomize.py  
\# this file can be anywhere in your Python path,  
\# but it usually goes in ${pythondir}/lib/site-packages/  
import sys  
sys.setdefaultencoding('utf_8')  

Эта системная кодировка является той, которая используется, когда вы используете функцию unicode ( "str" ) без каких-либо других параметров кодирования. С другой стороны, python пытается декодировать "str" в unicode на основе системной кодировки по умолчанию.

(3) Если вы используете IDLE или пиктон командной строки, я думаю, что ваша консоль отобразится в соответствии с системной кодировкой по умолчанию. По какой-то причине я использую pydev с eclipse, поэтому мне пришлось зайти в настройки моего проекта, отредактировать параметры конфигурации запуска моего теста script, перейти на вкладку "Общие" и сменить консоль с латинского-1 на utf-8 так что я мог визуально подтвердить, что я делаю, работает.

(4) Если вы хотите иметь некоторые тестовые строки, например

test_str = "ó"

в вашем исходном коде, тогда вам нужно будет указать python, какую кодировку вы используете в этом файле. (FYI: когда я ошибочно закодировал кодировку, которую мне пришлось на ctrl-Z, потому что мой файл стал нечитаемым.) Это легко осуществить, поместив строку так вверху вашего файла исходного кода:

# -*- coding: utf_8 -*-

Если у вас нет этой информации, python пытается проанализировать ваш код как ascii по умолчанию, и так:

SyntaxError: Non-ASCII character '\xf3' in file _redacted_ on line 81, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Как только ваша программа работает правильно или, если вы не используете консоль python или какую-либо другую консоль для просмотра вывода, тогда вам, вероятно, действительно будет только заботиться о # 1 в списке. Системное умолчание и консольное кодирование не так важны, если вам не нужно смотреть на вывод и/или использовать встроенную функцию unicode() (без каких-либо параметров кодирования) вместо функции string.decode(). Я написал демо-функцию, которую я вставляю в дно этого гигантского беспорядка, который, я надеюсь, правильно демонстрирует элементы в моем списке. Вот некоторые из результатов, когда я запускаю символ "-" через демонстрационную функцию, показывая, как различные методы реагируют на символ как на вход. Моя системная кодировка и вывод на консоль установлены для utf_8 для этого запуска:

'�' = original char <type 'str'> repr(char)='\xf3'
'?' = unicode(char) ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data
'ó' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'\xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

Теперь я изменю системную и консольную кодировку на latin_1, и я получу этот вывод для одного и того же ввода:

'ó' = original char <type 'str'> repr(char)='\xf3'
'ó' = unicode(char) <type 'unicode'> repr(unicode(char))=u'\xf3'
'ó' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'\xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

Обратите внимание, что символ "оригинал" отображается правильно, и встроенная функция unicode() работает сейчас.

Теперь я снова верну свой вывод на консоль utf_8.

'�' = original char <type 'str'> repr(char)='\xf3'
'�' = unicode(char) <type 'unicode'> repr(unicode(char))=u'\xf3'
'�' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'\xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

Здесь все по-прежнему работает так же, как в прошлый раз, но консоль не может отобразить вывод правильно. И т.д. В приведенной ниже функции также отображается дополнительная информация о том, что это и, мы надеемся, поможет кому-то выяснить, где разрыв в их понимании. Я знаю, что вся эта информация находится в других местах и ​​более подробно рассмотрена там, но я надеюсь, что это станет хорошей отправной точкой для тех, кто пытается получить кодирование с помощью python и/или sqlite. Идеи велики, но иногда исходный код может сэкономить вам день или два, пытаясь понять, какие функции делают.

Отказ от ответственности: я не специалист по кодированию, я собрал это вместе, чтобы помочь себе. Я продолжал строить его, когда я, вероятно, начал передавать функции в качестве аргументов, чтобы избежать избыточного кода, поэтому, если можно, я сделаю его более кратким. Кроме того, utf_8 и latin_1 никоим образом не являются единственными схемами кодирования, они - только те два, с которыми я играл, потому что я думаю, что они обрабатывают все, что мне нужно. Добавьте свои собственные схемы кодирования в демонстрационную функцию и протестируйте свой собственный вход.

Еще одна вещь: сумасшедшие разработчики приложений затрудняют жизнь в Windows.

#!/usr/bin/env python
# -*- coding: utf_8 -*-

import os
import sys

def encodingDemo(str):
    validStrings = ()
    try:        
        print "str =",str,"{0} repr(str) = {1}".format(type(str), repr(str))
        validStrings += ((str,""),)
    except UnicodeEncodeError as ude:
        print "Couldn't print the str itself because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",
        print ude
    try:
        x = unicode(str)
        print "unicode(str) = ",x
        validStrings+= ((x, " decoded into unicode by the default system encoding"),)
    except UnicodeDecodeError as ude:
        print "ERROR.  unicode(str) couldn't decode the string because the system encoding is set to an encoding that doesn't understand some character in the string."
        print "\tThe system encoding is set to {0}.  See error:\n\t".format(sys.getdefaultencoding()),  
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the unicode(str) because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",
        print uee
    try:
        x = str.decode('latin_1')
        print "str.decode('latin_1') =",x
        validStrings+= ((x, " decoded with latin_1 into unicode"),)
        try:        
            print "str.decode('latin_1').encode('utf_8') =",str.decode('latin_1').encode('utf_8')
            validStrings+= ((x, " decoded with latin_1 into unicode and encoded into utf_8"),)
        except UnicodeDecodeError as ude:
            print "The string was decoded into unicode using the latin_1 encoding, but couldn't be encoded into utf_8.  See error:\n\t",
            print ude
    except UnicodeDecodeError as ude:
        print "Something didn't work, probably because the string wasn't latin_1 encoded.  See error:\n\t",
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the str.decode('latin_1') because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",
        print uee
    try:
        x = str.decode('utf_8')
        print "str.decode('utf_8') =",x
        validStrings+= ((x, " decoded with utf_8 into unicode"),)
        try:        
            print "str.decode('utf_8').encode('latin_1') =",str.decode('utf_8').encode('latin_1')
        except UnicodeDecodeError as ude:
            print "str.decode('utf_8').encode('latin_1') didn't work.  The string was decoded into unicode using the utf_8 encoding, but couldn't be encoded into latin_1.  See error:\n\t",
            validStrings+= ((x, " decoded with utf_8 into unicode and encoded into latin_1"),)
            print ude
    except UnicodeDecodeError as ude:
        print "str.decode('utf_8') didn't work, probably because the string wasn't utf_8 encoded.  See error:\n\t",
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the str.decode('utf_8') because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",uee

    print
    print "Printing information about each character in the original string."
    for char in str:
        try:
            print "\t'" + char + "' = original char {0} repr(char)={1}".format(type(char), repr(char))
        except UnicodeDecodeError as ude:
            print "\t'?' = original char  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(char), repr(char), ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = original char  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(char), repr(char), uee)
            print uee    

        try:
            x = unicode(char)        
            print "\t'" + x + "' = unicode(char) {1} repr(unicode(char))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "\t'?' = unicode(char) ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = unicode(char)  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        try:
            x = char.decode('latin_1')
            print "\t'" + x + "' = char.decode('latin_1') {1} repr(char.decode('latin_1'))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "\t'?' = char.decode('latin_1')  ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = char.decode('latin_1')  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        try:
            x = char.decode('utf_8')
            print "\t'" + x + "' = char.decode('utf_8') {1} repr(char.decode('utf_8'))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "\t'?' = char.decode('utf_8')  ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = char.decode('utf_8')  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        print

x = 'ó'
encodingDemo(x)

Большое спасибо за ответы ниже и особенно @John Machin за то, что вы так хорошо ответили.

Ответ 1

Я все еще не знаю, есть ли способ правильно преобразовать '-' из латинского-1 в utf-8 и не калечить его

repr() и unicodedata.name() являются вашими друзьями, когда дело доходит до отладки таких проблем:

>>> oacute_latin1 = "\xF3"
>>> oacute_unicode = oacute_latin1.decode('latin1')
>>> oacute_utf8 = oacute_unicode.encode('utf8')
>>> print repr(oacute_latin1)
'\xf3'
>>> print repr(oacute_unicode)
u'\xf3'
>>> import unicodedata
>>> unicodedata.name(oacute_unicode)
'LATIN SMALL LETTER O WITH ACUTE'
>>> print repr(oacute_utf8)
'\xc3\xb3'
>>>

Если вы отправляете oacute_utf8 на терминал, настроенный для latin1, вы получите A-тильду, за которой следует верхний индекс-3.

Я переключился на строки Unicode.

Что вы называете строками Unicode? UTF-16?

Что дает? Прочитав это, описав точно ту же ситуацию, в которой я оказался, кажется, что совет состоит в том, чтобы игнорировать другой совет и использовать 8-битные байты в конце концов.

Я не представляю, как это кажется вам. Рассказ, который был передан, заключался в том, что юникодными объектами в кодировке Python и UTF-8 в базе данных были пути. Однако Мартин ответил на исходный вопрос, предложив метод ( "текст factory" ), чтобы OP мог использовать latin1 - это НЕ составляло рекомендации!

Обновить в ответ на эти дополнительные вопросы, поднятые в комментарии:

Я не понял, что символы Unicode по-прежнему содержат неявное кодирование. Я говорю это правильно?

Нет. Кодировка - это сопоставление между Unicode и чем-то другим, и наоборот. Символ Unicode не имеет кодировки, неявной или другой.

Мне кажется, что unicode ( "\ xF3" ) и "\ xF3".decode('latin1') одинаковы при оценке с помощью функции repr().

Скажи что? Мне это не похоже:

>>> unicode("\xF3")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf3 in position 0: ordinal
not in range(128)
>>> "\xF3".decode('latin1')
u'\xf3'
>>>

Возможно, вы имели в виду: u'\xf3' == '\xF3'.decode('latin1')... это, безусловно, верно.

Также верно, что unicode(str_object, encoding) выполняет то же самое, что и str_object.decode(encoding)..., включая вдувание, когда поставляется некорректное кодирование.

Это счастливое обстоятельство

Что первые 256 символов в Unicode одинаковы, код для кода, так как 256 символов в latin1 - хорошая идея. Поскольку все 256 возможных символов latin1 отображаются в Unicode, это означает, что ЛЮБЫЙ 8-разрядный байт, ANY Python str-объект может быть декодирован в unicode без исключения исключения. Это как и должно быть.

Однако существуют определенные люди, которые путают два совершенно разных понятия: "my script работает до завершения без каких-либо исключений" и "my script является безошибочным". Для них latin1 является "ловушкой и заблуждением".

Другими словами, если у вас есть файл, который действительно закодирован в cp1252 или gbk или koi8-u или что-то еще, и вы его декодируете с помощью latin1, получившийся Unicode будет полным мусором, а Python (или любой другой язык) не будет отмечать ошибка - он не знает, что вы совершили глупость.

или unicode ( "str" ), который всегда будет возвращать правильное декодирование?

Так же, как и при кодировке по умолчанию ascii, он вернет правильный юникод, если файл фактически закодирован в ASCII. В противном случае он взорвется.

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

Короче: ответ - нет.

Если нет, когда я получаю python str, который имеет любой возможный набор символов в нем, как я могу его декодировать?

Если объект str является допустимым XML-документом, он будет указан спереди. По умолчанию используется UTF-8. Если это правильно построенная веб-страница, она должна быть указана спереди (ищите "charset" ). К сожалению, многие авторы веб-страниц лежат через их зубы (ISO-8859-1 aka latin1, должен быть Windows-1252 aka cp1252, не тратьте ресурсы, пытающиеся декодировать gb2312, вместо этого используйте gbk). Вы можете получить подсказки от национальности/языка веб-сайта.

UTF-8 всегда стоит попробовать. Если данные ascii, это будет нормально работать, потому что ascii - это подмножество utf8. Строка текста, написанная с использованием символов, отличных от ascii, и была закодирована в кодировке, отличной от utf8, почти наверняка завершится с исключением, если вы попытаетесь декодировать ее как utf8.

Все приведенные выше эвристики, а также множество статистических данных заключены в chardet, модуль для угадывания кодировки произвольных файлов. Обычно это работает хорошо. Однако вы не можете сделать программное обеспечение идиотским. Например, если вы объединяете файлы данных, написанные на некоторых с кодировкой A, а некоторые с кодировкой B, и передаете результат в chardet, ответ скорее всего будет кодировать C с пониженным уровнем достоверности, например. 0.8. Всегда проверяйте доверительную часть ответа.

Если все остальное не выполнено:

(1) Попробуйте спросить здесь, с небольшим образцом от ваших данных... print repr(your_data[:400])... и любой информацией об обеспечении, которую вы получили.

(2) Недавнее российское исследование методов для восстановления забытых паролей, по-видимому, вполне применимо для вывода неизвестных кодировок.

Обновить 2 Кстати, не так ли, когда вы открыли еще один вопрос? -)

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

Это не Windows, которая это делает; это куча сумасшедших разработчиков приложений. Возможно, вы более понятно не перефразировали, но указали начальный абзац статьи об отбросе, о котором вы говорили:

Некоторые приложения добавляют символы CP1252 (Windows, Западная Европа) к документам, отмеченным как ISO 8859-1 (Latin 1) или другим кодировкам. Эти символы не являются действительными символами ISO-8859-1 и могут вызвать всевозможные проблемы при обработке и отображении приложений.

Фон:

Диапазон U + 0000 до U + 001F включительно обозначается в Unicode как "символы управления C0". Они также существуют в ASCII и latin1 с теми же значениями. Они включают такие знакомые вещи, как возврат каретки, подача строки, звонок, обратное пространство, вкладка и другие, которые используются редко.

Диапазон U + 0080 до U + 009F включительно обозначается в Unicode как "C1 Control Characters". Они также существуют в latin1 и включают 32 символа, которые никто из сторонних unicode.org не может представить для любого возможного использования.

Следовательно, если вы запустили счетчик символов в ваших данных в формате unicode или latin1, и вы найдете любые символы в этом диапазоне, ваши данные повреждены. Нет универсального решения; это зависит от того, как он стал поврежденным. Символы могут иметь то же значение, что и символы cp1252 в тех же позициях, и, следовательно, решение effbot будет работать. В другом случае, который я недавно рассматривал, хитроумные символы, по-видимому, были вызваны объединением текстовых файлов, закодированных в UTF-8, и другой кодировкой, которые необходимо было вывести на основе буквенных частот на (человеческом) языке, файлы были написано.

Ответ 2

UTF-8 - это стандартная кодировка баз данных SQLite. Это отображается в таких ситуациях, как "SELECT CAST (x'52C3B373" AS TEXT ")". Однако библиотека SQLite C на самом деле не проверяет правильность строки, вставленной в БД UTF-8.

Если вы вставляете объект unicode Python (или объект str в 3.x), библиотека sqlite3 Python автоматически преобразует его в UTF-8. Но если вы вставляете объект str, он просто предположит, что строка UTF-8, потому что Python 2.x "str" не знает его кодировки. Это одна из причин предпочтения строк Unicode.

Однако, это не поможет вам, если ваши данные сломаны для начала.

Чтобы исправить ваши данные, сделайте

db.create_function('FIXENCODING', 1, lambda s: str(s).decode('latin-1'))
db.execute("UPDATE TheTable SET TextColumn=FIXENCODING(CAST(TextColumn AS BLOB))")

для каждого текстового столбца в вашей базе данных.

Ответ 3

Я исправил эту проблему pysqlite, установив:

conn.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')

По умолчанию text_factory установлен в unicode(), который будет использовать текущую кодировку по умолчанию (ascii на моей машине)

Ответ 4

Конечно, есть. Но ваши данные уже разбиты в базе данных, поэтому вам нужно исправить это:

>>> print u'Sigur Rós'.encode('latin-1').decode('utf-8')
Sigur Rós

Ответ 5

Мои проблемы с Unicode с Python 2.x(для конкретного Python 2.7.6) исправили это:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

Он также решил ошибку, которую вы упоминаете в начале сообщения:

sqlite3.ProgrammingError: вы не должны использовать 8-битные байты, если...

ИЗМЕНИТЬ

sys.setdefaultencoding - грязный хак. Да, он может решить проблемы UTF-8, но все идет с ценой. Для получения дополнительной информации см. Следующие ссылки: