当前位置:  开发笔记 > 编程语言 > 正文

SQLite,python,unicode和非utf数据

如何解决《SQLite,python,unicode和非utf数据》经验,为你挑选了4个好方法。

我开始尝试使用python在sqlite中存储字符串,并得到消息:

sqlite3.ProgrammingError:除非使用可解释8位字节串的text_factory(如text_factory = str),否则不得使用8位字节串.强烈建议您只需将应用程序切换为Unicode字符串.

好的,我切换到Unicode字符串.然后我开始收到消息:

sqlite3.OperationalError:无法使用文本'SigurRós'解码为UTF-8列'tag_artist'

尝试从数据库中检索数据时.更多的研究和我开始在utf8编码,但随后'SigurRós'开始看起来像'SigurRós'

注意: @John Machin指出,我的控制台设置为'latin_1'显示.

是什么赋予了?在阅读完这篇文章后,描述了我所处的完全相同的情况,似乎建议忽略其他建议并且毕竟使用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')

或者如果str已经是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一样.但是,有可能你可以将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或命令行python,我认为您的控制台将根据默认的系统编码显示.我出于某种原因使用带有eclipse的pydev,所以我必须进入我的项目设置,编辑测试脚本的启动配置属性,转到Common选项卡,然后将控制台从latin-1更改为utf-8,以便我可以直观地确认我在做什么工作.

(4)如果你想要一些测试字符串,例如

test_str = "ó"

在你的源代码中,你必须告诉python你在该文件中使用了什么样的编码.(仅供参考:当我输入错误编码时,我必须使用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  repr(char)='\xf3'
'?' = unicode(char) ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data
'ó' = char.decode('latin_1')  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  repr(char)='\xf3'
'ó' = unicode(char)  repr(unicode(char))=u'\xf3'
'ó' = char.decode('latin_1')  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  repr(char)='\xf3'
'?' = unicode(char)  repr(unicode(char))=u'\xf3'
'?' = char.decode('latin_1')  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> John Machin..:

我仍然不知道是否有办法正确地将'ó'从latin-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-tilde,然后是superscript-3.

我切换到Unicode字符串.

你在叫什么叫Unicode字符串?UTF-16?

是什么赋予了?在阅读完这篇文章后,描述了我所处的完全相同的情况,似乎建议忽略其他建议并且毕竟使用8位字节串.

我无法想象对你来说这是怎么回事.正在传达的故事是Python中的unicode对象和数据库中的UTF-8编码是要走的路.然而,Martin回答了原来的问题,为OP提供了一个方法("文本工厂"),以便能够使用latin1 - 这不构成推荐!

针对评论中提出的其他问题进行更新:

我不明白unicode字符仍包含隐式编码.我说的没错吗?

不.编码是Unicode与其他内容之间的映射,反之亦然.Unicode字符没有隐式或其他编码.

它看起来像unicode("\ xF3")和"\ xF3".decode('latin1')在使用repr()进行评估时是相同的.

说什么?它看起来不像我:

>>> unicode("\xF3")
Traceback (most recent call last):
  File "", line 1, in 
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而不会引发异常.这是应该的.

然而,有些人混淆了两个完全不同的概念:"我的脚本运行完成,没有任何异常被提出"和"我的脚本没有错误".对他们来说,latin1是"陷阱和妄想".

换句话说,如果你有一个实际上用cp1252或gbk或koi8-u编码的文件,或者你使用latin1解码它,那么生成的Unicode将是完全垃圾,Python(或任何其他语言)不会标记错误 - - 它无法知道你已经愚蠢了.

或是unicode("str")总是返回正确的解码?

就像这样,默认编码为ascii,如果文件实际上是用ASCII编码的,它将返回正确的unicode.否则,它会爆炸.

同样,如果您指定了正确的编码,或者是正确编码的超集,您将获得正确的结果.否则你会得到胡言乱语或异常.

简而言之:答案是否定的.

如果没有,当我收到一个有任何可能的字符集的python str时,我怎么知道如何解码呢?

如果str对象是有效的XML文档,则会预先指定它.默认值为UTF-8.如果它是一个正确构建的网页,应该预先指定(查找"charset").不幸的是网页的许多作家骗通过他们的牙齿(ISO-8859-1又名latin1的,应该是Windows 1252又名CP1252;不浪费资源,试图解码GB2312,GBK使用代替).您可以从网站的国籍/语言中获取线索.

UTF-8总值得一试.如果数据是ascii,它将正常工作,因为ascii是utf8的子集.如果您尝试将其解码为utf8,那么使用非ascii字符编写并且使用utf8以外的编码进行编码的文本字符串几乎肯定会失败并出现异常.

所有上述启发式以及更多和大量统计信息都封装在chardet中,这是一个用于猜测任意文件编码的模块.它通常运作良好.但是你不能使软件具有防止傻瓜的功能.例如,如果连接一些用编码A编写的数据文件和一些用编码B编写的数据文件,并将结果提供给chardet,那么答案很可能是以较低的置信度编码C ,例如0.8.始终检查答案的置信度部分.

如果一切都失败了:

(1)尝试在这里询问,从您的数据前面加上一小部分样本print repr(your_data[:400])......以及有关其出处的任何附属信息.

(2)俄罗斯最近对恢复被遗忘密码的技术的研究似乎非常适用于推断未知编码.

更新2 BTW,是不是你打开另一个问题的时间? - )

还有一件事:Windows显然使用的字符是某些字符的Unicode,这些字符不是该字符的正确Unicode,因此如果要在其他程序中使用它们,则可能必须将这些字符映射到正确的字符.期待那些角色在正确的位置.

这不是Windows正在做的事情; 这是一群疯狂的应用程序开发人员.您可能有更多可以理解的解释,但引用了您提到的effbot文章的开头段落:

某些应用程序将CP1252(Windows,西欧)字符添加到标记为ISO 8859-1(Latin 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的解决方案将起作用.在我一直在寻找最近另一起案件中,狡猾的人物似乎已经在UTF-8编码的串联文本文件和其它的编码,这需要基于(人)的字母频率来推断语言的文件引起了写的.



2> dan04..:

UTF-8是SQLite数据库的默认编码.这出现在"SELECT CAST(x'52C3B373'AS TEXT);"的情况下.但是,SQLite C库实际上不会检查插入到DB中的字符串是否为有效的UTF-8.

如果插入Python unicode对象(或3.x中的str对象),Python sqlite3库将自动将其转换为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> hoju..:

我通过设置修复了这个pysqlite问题:

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

默认情况下,text_factory设置为unicode(),它将使用当前的默认编码(在我的机器上为ascii)



4> Ignacio Vazq..:

当然有.但是您的数据已在数据库中中断,因此您需要修复它:

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

推荐阅读
kikokikolove
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有