SQLite,python,unicode和非utf数据

SQLite, python, unicode, and non-utf data

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

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 r"?3S’

注意:正如@john machin指出的那样,我的控制台被设置为以"拉丁语1"显示。

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

在我开始这个过程之前,我对Unicode和UTF不太了解。在过去的几个小时里,我学到了很多东西,但我仍然不知道是否有一种方法可以正确地将"_"从拉丁语1转换为UTF-8,而不是把它弄乱。如果没有,为什么sqlite会"强烈推荐"我的应用程序切换到Unicode字符串?

我将用一个总结和一些示例代码来更新这个问题,这些代码是我在过去24小时内学到的所有知识,这样我的鞋子里的人就可以有一个简单的(ER)指南。如果我发布的信息有任何错误或误导,请告诉我,我会更新,或者你们中的一个高级人员可以更新。

答案摘要

让我先说明我理解的目标。在处理各种编码时,如果您试图在它们之间转换,那么目标是了解您的源编码是什么,然后使用该源编码将其转换为Unicode,然后将其转换为所需的编码。Unicode是一个基,编码是该基子集的映射。utf_8为Unicode中的每个字符都留出了空间,但因为它们与拉丁语_1不在同一个位置,例如,用utf_8编码并发送到拉丁语_1控制台的字符串看起来不像您期望的那样。在python中,使用unicode和其他编码的过程如下:

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

或者如果str已经是unicode格式

1
str.encode('desired_encoding')

对于sqlite,我实际上不想再次对其进行编码,我想对其进行解码,并将其保留为Unicode格式。当您尝试使用Unicode和Python中的编码时,您可能需要注意以下四点。

  • 要使用的字符串的编码,以及要使用的编码。
  • 系统编码。
  • 控制台编码。
  • 源文件的编码
  • 详述:

    (1)当您从源读取字符串时,它必须有一些编码,比如拉丁语1或UTF 8。在我的例子中,我从文件名中获取字符串,所以不幸的是,我可以得到任何类型的编码。WindowsXP使用UCS-2(Unicode系统)作为其本机字符串类型,这在我看来像是作弊。对我来说,幸运的是,大多数文件名中的字符不会由多个源编码类型组成,我认为我所有的字符要么完全是拉丁语的1,完全是UTF的8,要么只是纯ASCII(这两者都是其中的一个子集)。所以我只是把它们读出来然后解码,就好像它们仍然是拉丁语1或是UTF 8。不过,在Windows的文件名中,可能会混合使用拉丁语1和utf 8以及其他任何字符。有时,这些字符可以显示为框,有时它们看起来只是残缺的,有时它们看起来是正确的(重音字符和其他什么)。继续前进。

    (2)python有一个默认的系统编码,它在python启动时设置,并且在运行时不能更改。详情请参阅此处。脏摘要…这是我添加的文件:

    1
    2
    3
    4
    5
    \# 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,所以我必须进入我的项目设置,编辑测试脚本的启动配置属性,转到公共选项卡,然后将控制台从Latin-1更改为UTF-8,这样我就可以直观地确认我正在做的工作。

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

    1
    test_str ="ó"

    在源代码中,那么您必须告诉python您在该文件中使用的是哪种编码。(仅供参考:当我输入错误的编码时,我必须按ctrl-z,因为我的文件变得不可读。)这很容易通过在源代码文件的顶部放一行这样的代码来实现:

    1
    # -*- coding: utf_8 -*-

    如果您没有这些信息,那么默认情况下,python会尝试将代码解析为ascii,因此:

    1
    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。系统缺陷


    I'm still ignorant of whether there is a way to correctly convert 'ó' from latin-1 to utf-8 and not mangle it

    Ok.

    repr()和unicodedata.name()是调试此类问题的朋友:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> 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_tf8发送到为Latin1设置的终端,您将得到a-tilde后跟上标-3。好的。

    I switched to Unicode strings.

    Ok.

    您在调用什么unicode字符串?UTF?16?好的。

    What gives? After reading this, describing exactly the same situation I'm in, it seems as if the advice is to ignore the other advice and use 8-bit bytestrings after all.

    Ok.

    我无法想象你是怎么想的。所传达的故事是,使用Python和数据库中的UTF-8编码的Unicode对象是解决问题的方法。然而,马丁回答了最初的问题,给出了一种方法("文本工厂"),使OP能够使用拉丁语1——这并不构成建议!好的。

    针对评论中提出的这些进一步问题进行更新:好的。

    I didn't understand that the unicode characters still contained an implicit encoding. Am I saying that right?

    Ok.

    不。编码是Unicode和其他东西之间的映射,反之亦然。Unicode字符没有隐式或其他编码。好的。

    It looks to me like unicode("\xF3") and"\xF3".decode('latin1') are the same when evaluated with repr().

    Ok.

    说什么?在我看来不像这样:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 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)的做法是一样的。包括在提供不适当的编码时爆炸。好的。

    Is that a happy circumstance

    Ok.

    Unicode中的前256个字符(代码)与Latin1中的256个字符相同,这是一个好主意。因为所有256个可能的Latin1字符都映射到Unicode,这意味着任何8位字节、任何python str对象都可以解码为Unicode,而不会引发异常。这是应该的。好的。

    然而,有些人混淆了两个完全不同的概念:"我的脚本运行到完成,没有任何异常被提出"和"我的脚本没有错误"。对他们来说,拉丁裔是"一个陷阱和错觉"。好的。

    换句话说,如果您有一个文件实际上是用CP1252或GBK或Koi8-U编码的,并且您使用Latin1对其进行解码,那么得到的Unicode将是垃圾,而python(或任何其他语言)将不会标记错误——它无法知道您犯了一个愚蠢的错误。好的。< Buff行情>

    还是unicode("str")总是返回正确的解码?好的。< /块引用>

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

    同样,如果您指定了正确的编码,或者指定了正确编码的超集,您将得到正确的结果。否则你会变得胡言乱语或是有例外。好的。

    简而言之:答案是否定的。好的。

    If not, when I receive a python str that has any possible character set in it, how do I know how to decode it?

    Ok.

    如果str对象是有效的XML文档,那么它将被预先指定。默认为UTF-8。如果它是一个正确构建的网页,那么应该在前面指定(查找"charset")。不幸的是,许多网页作者都是咬牙切齿(iso-8859-1又名latin1,应该是windows-1252又名cp1252;不要浪费资源来解码gb2312,而是使用gbk)。你可以从网站的国籍/语言中获得线索。好的。

    UTF-8总是值得尝试的。如果数据是ASCII,那么它就可以正常工作,因为ASCII是UTF8的一个子集。使用非ASCII字符编写的、用非UTF8编码的文本字符串几乎肯定会失败,但如果您尝试将其解码为UTF8,则会出现异常。好的。

    以上所有的启发式方法以及越来越多的统计数据都封装在chardet中,chardet是一个猜测任意文件编码的模块。它通常工作得很好。但是你不能让软件成为白痴的证据。例如,如果将一些使用编码A编写的数据文件和一些使用编码B编写的数据文件连接起来,并将结果反馈给chardet,那么答案很可能是使用降低的置信度(例如0.8)对C进行编码。始终检查答案的置信度部分。好的。

    如果其他都失败:好的。

    (1)试着在这里询问,从你的数据前面取一个小样本……print repr(your_data[:400])…以及关于它的出处的任何附带信息。好的。

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

    更新2 btw,是不是该你打开另一个问题了?-)好的。

    One more thing: there are apparently characters that Windows uses as Unicode for certain characters that aren't the correct Unicode for that character, so you may have to map those characters to the correct ones if you want to use them in other programs that are expecting those characters in the right spot.

    Ok.

    不是Windows做的,而是一群疯狂的应用程序开发人员。您可能更容易理解,没有解释,但引用了您提到的effbot文章的开头段落:好的。型

    Some applications add CP1252 (Windows, Western Europe) characters to documents marked up as ISO 8859-1 (Latin 1) or other encodings. These characters are not valid ISO-8859-1 characters, and may cause all sorts of problems in processing and display applications.

    Ok.

    背景:好的。型

    范围U+0000至U+001F(含)用Unicode表示为"c0控制字符"。它们也存在于ASCII和Latin1中,具有相同的含义。它们包括诸如回车、换行、钟形、退格、制表符和其他很少使用的类。好的。型

    范围U+0080至U+009F(含)用Unicode指定为"C1控制字符"。这些字符也存在于拉丁语1中,其中包含32个字符,而unicode.org之外的任何人都无法想象使用这些字符。好的。型

    因此,如果对Unicode或Latin1数据运行字符频率计数,并且在该范围内找到任何字符,则说明数据已损坏。没有通用的解决方案,这取决于它是如何被破坏的。字符的含义可能与CP1252字符在同一位置上的含义相同,因此Effbot的解决方案将起作用。在我最近看到的另一个例子中,不可靠的字符似乎是由连接用UTF-8编码的文本文件和另一种编码引起的,这种编码需要根据文件所用(人类)语言中的字母频率推导出来。好的。型好啊。


    UTF-8是SQLite数据库的默认编码。这在"select cast(x'52c3b373'作为文本);"等情况下显示。但是,sqlite c库实际上并不检查插入数据库的字符串是否是有效的utf-8。

    如果插入一个python unicode对象(或3.x中的str对象),python sqlite3库将自动将其转换为utf-8。但是,如果插入str对象,它只会假设字符串是utf-8,因为python 2.x"str"不知道它的编码。这是首选Unicode字符串的原因之一。

    但是,如果您的数据在开始时被破坏,它不会帮助您。

    要修复数据,请执行以下操作

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

    数据库中的每个文本列。


    我通过设置:

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

    默认情况下,文本工厂设置为unicode(),它将使用当前默认编码(我的机器上的ASCII)


    当然有。但数据库中的数据已被破坏,因此您需要修复它:

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

    我对python 2.x(具体来说是python 2.7.6)的unicode问题解决了这个问题:

    1
    2
    3
    4
    5
    6
    7
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

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

    它还解决了您在文章开头提到的错误:

    sqlite3.ProgrammingError: You must not use 8-bit bytestrings unless
    ...

    编辑

    sys.setdefaultencoding是一个肮脏的黑客。是的,它可以解决UTF-8问题,但一切都有代价。有关详细信息,请参阅以下链接:

    • 为什么sys.setDefaultEncoding()会破坏代码
    • 为什么在py脚本中需要sys.setdefaultencoding("utf-8")?