sqlite3.ProgrammingError: You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.
好的,我切换到Unicode字符串。然后我开始收到以下消息:
sqlite3.OperationalError: Could not decode to UTF-8 column 'tag_artist' with text 'Sigur Rós'
在尝试从数据库检索数据时。进一步调查后,我开始使用utf8进行编码,但是'Sigur Rós'开始变成了'Sigur Rós'
注意:正如@ John Machin指出的那样,我的控制台设置为以'latin_1'显示。
这是什么情况?阅读this之后,描述了与我处于完全相同的情况,似乎建议忽略其他建议,最终仍然使用8位字节串。
在开始这个过程之前,我对unicode和utf不太了解。在过去的几个小时里,我学到了很多,但我仍然不知道是否有一种正确的方法将来自latin-1的'ó'转换为utf-8而不会弄乱它。如果没有,为什么sqlite会“强烈建议”我将应用程序切换到Unicode字符串?
我打算更新这个问题,总结一下我在过去24小时学到的一切,并提供一些示例代码,以便像我这样的人可以有一个更简单的指南。如果我发布的信息有误或误导,请告诉我,我会进行更新,或者你们资深的大佬之一可以进行更新。
答案概述
首先让我表明我所理解的目标。如果你想在不同编码之间进行转换,处理各种编码的目标是要了解你的源编码是什么,然后使用该源编码将其转换为unicode,然后将其转换为所需的编码。Unicode是一个基础,而编码是该基础上子集的映射。utf_8可以容纳unicode中的每个字符,但因为它们不像latin_1一样位于相同位置,因此用utf_8编码的字符串发送到latin_1控制台将不会看起来像您所期望的那样。在Python中,从获取unicode到进入另一种编码的过程如下:
str.decode('source_encoding').encode('desired_encoding')
如果字符串已经是Unicode格式
str.encode('desired_encoding')
对于sqlite,我实际上不想再次对其进行编码,而是希望对其进行解码并以unicode格式保留。以下是在python中使用unicode和编码时需要注意的四件事。
- 您要处理的字符串的编码以及您要将其转换为的编码。
- 系统编码。
- 控制台编码。
- 源文件的编码
(1) 当您从源中读取字符串时,它必须具有某种编码,例如latin_1或utf_8。在我的情况下,我从文件名中获取字符串,所以不幸的是,我可能会得到任何种类的编码。Windows XP使用UCS-2(一种Unicode系统)作为其本地字符串类型,这似乎对我来说是欺骗行为。幸运的是,大多数文件名中的字符不会由多种源编码类型组成,而且我认为所有我的文件名都是完全的latin_1、完全的utf_8或纯粹的ascii(它是这两者的子集)。因此,我只是读取它们并像它们仍然在latin_1或utf_8中一样对它们进行了解码。但是,您可能会在Windows上的文件名中混合使用latin_1、utf_8和其他字符。有时这些字符会显示为方框,其他时候它们看起来混乱,而其他时候它们看起来正确(带重音符号的字符等)。继续。
(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或命令行Python,则我认为您的控制台将根据默认系统编码显示。由于某种原因,我正在使用带有Eclipse的pydev,因此我必须进入项目设置,编辑测试脚本的启动配置属性,转到Common选项卡,并将控制台从latin-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
注意,'original'字符现在可以正确显示,并且内置的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的全面回答。