SQLite,python,unicode和非UTF数据
我开始尝试使用python在sqlite中存储string,并得到消息:
sqlite3.ProgrammingError:除非使用可解释8位字节串的text_factory(如text_factory = str),否则不得使用8位字节串。 强烈build议您将应用程序切换为Unicodestring。
好的,我切换到Unicodestring。 然后我开始收到消息:
sqlite3.OperationalError:无法解码到UTF-8列'tag_artist'与文字'SigurRós'
当试图从数据库中检索数据。 更多的研究,我开始用utf8编码,但是“SigurRós”开始看起来像“Sigur R?s”
注意:我的控制台被设置为显示在“latin_1”,正如@John Machin指出的那样。
是什么赋予了? 读完这个之后 ,描述完全一样的情况,好像build议是忽略其他的build议,并使用8位字节串。
在我开始这个过程之前,我对Unicode和UTF不太了解。 在过去的几个小时里,我学到了很多东西,但是我仍然不知道是否有办法将“ó”从拉丁文-1正确地转换为utf-8,而不是将其摧毁。 如果没有,为什么会强烈build议我将应用程序切换到unicodestring?
我将用一个总结和一些示例代码来更新这个问题,这些代码是我在过去24小时学到的一切,这样我的鞋子里有人可以有一个简单的指导。 如果我发布的信息有任何错误或误导,请告诉我,我会更新,或者你们其中一位高级人员可以更新。
答案摘要
让我先说明我的理解。 处理各种编码的目标是,如果你想在它们之间进行转换,就要理解你的源编码是什么,然后使用该源编码将其转换为unicode,然后将其转换为你想要的编码。 Unicode是基础,编码是该基础子集的映射。 utf_8为unicode中的每个字符提供了空间,但是因为它们与latin_1不在同一个地方,所以使用utf_8编码并发送到latin_1控制台的string将看起来不像您期望的那样。 在python中获得unicode和另一种编码的过程如下所示:
str.decode('source_encoding').encode('desired_encoding')
或者如果str已经在unicode中
str.encode('desired_encoding')
对于sqlite我实际上并不想重新编码,我想解码它,并保持unicode格式。 在尝试使用unicode和python编码时,需要注意以下四点。
- 您想要使用的string编码,以及您想要的编码。
- 系统编码。
- 控制台编码。
- 源文件的编码
阐述:
(1)从源读取string时,它必须有一些编码,如latin_1或utf_8。 在我的情况下,我从文件名获得string,所以不幸的是,我可以得到任何types的编码。 Windows XP使用UCS-2(一个Unicode系统)作为它的本地stringtypes,这似乎是对我的欺骗。 对我来说幸运的是,大多数文件名中的字符不会由多个源编码types组成,我认为我的所有字符都完全是latin_1,完全是utf_8,或者只是简单的ascii(它是那些)。 所以我只读了它们,并将它们解码,好像它们仍然在latin_1或utf_8中。 不过,有可能你可以将latin_1和utf_8以及其他任何字符混合在一个文件名中。 有时这些angular色可以显示为框,其他时候他们只是看起来被打乱,而其他时候他们看起来是正确的(重音字符和什么)。 继续。
(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,我认为你的控制台将根据默认的系统编码显示。 由于某种原因,我使用pydev和eclipse,所以我不得不进入我的项目设置,编辑我的testing脚本的启动configuration属性,转到Common选项卡,并将控制台从latin-1更改为utf-8,以便我可以直观地确认我在做什么工作。
(4)如果你想有一些testingstring,例如
test_str = "ó"
在你的源代码中,那么你将不得不告诉python你在那个文件中使用了什么样的编码。 (仅供参考:当我错误地input一个编码时,我必须按ctrl-Z,因为我的文件变得不可读)。通过在源代码文件的顶部放置一行,就可以轻松实现这一点:
# -*- coding: utf_8 -*-
如果你没有这个信息,python会默认以asciiparsing你的代码,所以:
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()函数。 我写了一个演示函数,我将粘贴到这个巨大的混乱的底部,我希望正确地演示我的列表中的项目。 当我通过演示函数运行字符'ó'时,这里是一些输出,显示了各种方法如何对字符作为input作出反应。 我的系统编码和控制台输出都设置为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,并得到相同input的输出:
'ó' = 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
这里的一切仍然和上次一样,但控制台无法正确显示输出。 等下面的function还显示更多的信息,并希望能帮助人们找出他们理解的差距在哪里。 我知道所有这些信息都在其他地方,并在那里得到了更加彻底的处理,但是我希望对于那些试图用python和/或sqlite编码的人来说,这将是一个很好的开端。 想法是伟大的,但有时源代码可以节省你一两天试图找出什么function做什么。
免责声明:我不是编码专家,我把它放在一起来帮助我自己的理解。 当我开始传递函数作为参数来避免这么多的冗余代码的时候,我一直在build立它,所以如果我可以的话,我会使它更简洁。 另外,utf_8和latin_1绝不是唯一的编码scheme,它们只是我玩过的两个,因为我认为它们处理我需要的一切。 将您自己的编码scheme添加到演示function并testing您自己的input。
还有一件事情: 显然有疯狂的应用程序开发人员在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正确转换为utf-8,而不是将其捣毁
在debugging这样的问题时,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设置的terminal,则会得到A-tilde,然后是superscript-3。
我切换到Unicodestring。
你在调用Unicodestring是什么? UTF-16?
是什么赋予了? 读完这个之后,描述完全一样的情况,好像build议是忽略其他的build议,并使用8位字节串。
我无法想象你是怎么看的。 正在传达的故事是Python中的unicode对象和数据库中的UTF-8编码是要走的路。 然而,马丁回答了原来的问题,给OP的一个方法(“文本工厂”)能够使用latin1 – 这不构成一个build议!
根据评论中提出的这些进一步的问题更新 :
我不明白,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)
相同也是如此,包括当提供不适当的编码时会炸毁。
这是一个快乐的情况
Unicode中的前256个字符是相同的代码,因为latin1中的256个字符是个好主意。 因为所有256个可能的latin1字符都映射到Unicode,这意味着任何8位字节,任何Python str对象都可以解码为unicode,而不会引发exception。 这是应该的。
但是也有一些人混淆了两个完全不同的概念:“我的脚本运行完毕,没有任何例外”,“我的脚本没有错误”。 对他们来说,拉丁美洲是“一个网罗和妄想”。
换句话说,如果你有一个文件实际上是用cp1252或者gbk或者koi8-u编码的,或者你用latin1来解码它,那么得到的Unicode将是完全垃圾,Python(或者其他语言)不会标记错误 – – 它无法知道你犯了一个愚蠢的行为。
或者是unicode(“str”)总是返回正确的解码?
就像那样,默认的编码是ascii,如果文件实际上是用ASCII编码的话,它将返回正确的unicode。 否则,它会炸毁。
同样,如果您指定了正确的编码,或者是正确编码的超集,那么您将得到正确的结果。 否则,你会得到胡言乱语或exception。
总之:答案是否定的。
如果没有,当我收到一个可能的字符集的Python str,我怎么知道如何解码?
如果str对象是一个有效的XML文档,它将被预先指定。 默认是UTF-8。 如果它是一个正确构build的网页,应该在前面指定(查找“charset”)。 不幸的是,许多网页作家通过他们的牙齿(ISO-8859-1 aka latin1,应该是Windows-1252又名cp1252;不要浪费资源试图解码gb2312,而是使用gbk代替)。 你可以从网站的国籍/语言中得到线索。
UTF-8总是值得一试。 如果数据是ascii,那么它可以正常工作,因为ascii是utf8的一个子集。 如果您尝试将其解码为utf8,那么使用非ASCII字符编码且使用非utf8编码进行编码的文本string几乎肯定会失败,并显示exception。
所有上面的启发式和更多的统计信息都被封装在chardet中 ,这是一个猜测任意文件编码的模块。 它通常运作良好。 但是你不能使软件白痴certificate。 例如,如果连接用编码A编写的一些数据文件,用编码B连接一些数据文件并将结果提供给chardet,那么答案很可能是编码C 的置信度降低,例如0.8。 总是检查答案的信心部分 。
如果一切都失败了:
(1)在这里试着问一下,在你的数据前面有一个小样本… print repr(your_data[:400])
…以及关于它的出处的附带信息。
(2)最近俄罗斯对恢复被遗忘密码技术的研究似乎非常适用于推导未知编码。
更新2顺便说一下,是不是关于你打开另一个问题的时间?
还有一件事:Windows显然使用Unicode作为Unicode字符,这些字符不是该字符的正确Unicode字符,所以如果您想在其他程序中使用它们,您可能必须将这些字符映射到正确的字符期待那些angular色在正确的位置。
这不是Windows, 这是一群疯狂的应用程序开发人员。 你可能更容易理解,但引用你提到的effbot文章的开头一段:
某些应用程序将CP1252(Windows,西欧)字符添加到标记为ISO 8859-1(拉丁语1)或其他编码的文档中。 这些字符不是有效的ISO-8859-1字符,并且可能会在处理和显示应用程序时导致各种问题。
背景:
U + 0000到U + 001F的范围在Unicode中被指定为“C0控制字符”。 这些也存在于ASCII和latin1中,具有相同的含义。 它们包括诸如回车,换行,钟,退格,标签等很多常用的东西。
U + 0080到U + 009F的范围在Unicode中被指定为“C1控制字符”。 这些也存在于latin1中,并且包括32个字符,unicode.org之外没有人可以想象任何可能的用途。
因此,如果在unicode或latin1数据上运行字符频率计数,并且发现该范围内有任何字符,则说明数据已损坏。 没有普遍的解决办法; 这取决于它是如何被损坏的。 这些字符可能与cp1252字符在相同位置上具有相同的含义,因此effbot的解决scheme将起作用。 在我最近看到的另一个案例中,这些狡猾的字符似乎是由连接UTF-8编码的文本文件和另一种编码引起的,这些编码需要根据(人)语言中的字母频率推断出来写入。
UTF-8是SQLite数据库的默认编码。 这种情况出现在“SELECT CAST(x'52C3B373'AS TEXT)”的情况下。 但是,SQLite C库实际上并不检查插入到数据库中的string是否是有效的UTF-8。
如果插入一个Python unicode对象(或3.x中的str对象),Python sqlite3库将自动将其转换为UTF-8。 但是如果你插入一个str对象,它会假设string是UTF-8,因为Python 2.x“str”不知道它的编码。 这是喜欢Unicodestring的一个原因。
但是,如果您的数据被打破,它不会帮助您。
要修正您的数据,请执行
db.create_function('FIXENCODING', 1, lambda s: str(s).decode('latin-1')) db.execute("UPDATE TheTable SET TextColumn=FIXENCODING(CAST(TextColumn AS BLOB))")
为数据库中的每个文本列。
我通过设置来解决这个pysqlite问题:
conn.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')
默认情况下,text_factory被设置为unicode(),它将使用当前的默认编码(我的机器上是ascii)
当然有。 但是你的数据已经在数据库中被破解了,所以你需要修复它:
>>> print u'Sigur Rós'.encode('latin-1').decode('utf-8') Sigur Rós
我的Python 2.x(Python 2.7.6具体)unicode问题解决了这个问题:
#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import unicode_literals import sys reload(sys) sys.setdefaultencoding('utf-8')
它也解决了你在post开头提到的错误:
sqlite3.ProgrammingError:您不能使用8位字节串,除非…
编辑
sys.setdefaultencoding
是一个肮脏的黑客 。 是的,它可以解决UTF-8问题,但一切都是有代价的。 有关更多详情,请参阅以下链接:
- 为什么sys.setdefaultencoding()会破坏代码
- 为什么我们需要在py脚本中使用sys.setdefaultencoding(“utf-8”)?