2

我有一个小的 Python 程序,它从文件中读取 SQL 语句并在 MySQL 数据库上运行它们。该文件以 UTF-8 编码,数据库也使用 UTF-8。

如果我不设置数据库编码,我会得到每个人都问的关于“'latin-1'编解码器无法编码字符......”的常见错误。所以我使用设置数据库和文件编码

con.set_character_set('utf8')
fh = codecs.open(fname,'r','utf8')

现在它可以工作了,但是当我不设置文件编码(或仅使用内置打开)时,它也可以工作,就在数据库中。“工作”是指生成的数据库记录在假设 UTF-8 的 WordPress 中正确显示。

如果我想要魔法,我会用 Ruby 编写代码。在这种情况下,Python 在做什么,为什么不需要告诉它文件编码?

不用说我在这方面做了很多搜索,而且我的 Google-foo 通常都很好。这里和博客中有大量关于为什么需要设置编码以及如何设置编码的帖子,但我没有找到任何关于它为什么有时会起作用的信息。

编辑:我使用包含“谢谢”的文件对此进行了简单的测试。</p>

file
  E2 80 9C 54 68 61 6E 6B 20 79 6F 75 2E E2 80 9D
codecs utf8
  201C 54 68 61 6E 6B 20 79 6F 75 2E 201D

尝试使用 codecs.open(myfile,'r','ascii') 读取它返回“UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2”

从文件中读取产生了一个字节字符串,所以看起来魔法正在发生在插入数据库中。

4

2 回答 2

1

当你使用

fh = codecs.open(fname,'r','utf8')

fh.read()返回一个 Unicode。如果你使用这个 unicode 并使用你的数据库驱动程序(例如 mysql-python)将数据插入到你的数据库中,那么驱动程序负责将 unicode 转换为字节。驱动程序正在使用由设置的编码

con.set_character_set('utf8')

如果你使用

fh = open(fname, 'r')

然后fh.read()返回一个字节串。您将受到任何字节的摆布fname。幸运的是,根据您的帖子,该文件以 UTF-8 编码。由于数据已经是一串字节,驱动程序不执行任何编码,只是将字节串原样传送给数据库。

无论哪种方式,相同的 UTF-8 编码字节字符串都会插入到数据库中。


让我们看一下定义codecs.open的源代码:

def open(filename, mode='rb', encoding=None, errors='strict', buffering=1):

    if encoding is not None:
        if 'U' in mode:
            # No automatic conversion of '\n' is done on reading and writing
            mode = mode.strip().replace('U', '')
            if mode[:1] not in set('rwa'):
                mode = 'r' + mode
        if 'b' not in mode:
            # Force opening of the file in binary mode
            mode = mode + 'b'
    file = __builtin__.open(filename, mode, buffering)
    if encoding is None:
        return file
    info = lookup(encoding)
    srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors)
    # Add attributes to simplify introspection
    srw.encoding = encoding
    return srw

特别注意如果设置了 no 会发生什么encoding

file = __builtin__.open(filename, mode, buffering)
if encoding is None:
     return file

所以本质上与未设置编码时codecs.open的内置相同。open内置open函数返回一个文件对象,其read方法返回一个str对象。它根本不解码。

相反,当您指定编码时,将codecs.open返回 aStreamReaderWritersrw.encoding设置为encoding。现在,当您调用StreamReaderWriter'sread方法时,通常会返回一个unicode对象。首先str对象必须使用指定的编码进行解码。

在您的示例中,str对象是

In [19]: content
Out[19]: '\xe2\x80\x9cThank you.\xe2\x80\x9d'

如果您将编码指定为'ascii',则StreamReaderWriter尝试content使用'ascii'编码进行解码:

In [20]: content.decode('ascii')

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

这并不奇怪,因为ascii编码只能解码 0--127 范围内的字节,并且'\xe2'中的第一个字节content具有该范围之外的序数值。


具体而言:当您不指定编码时

In [13]: with codecs.open(filename, 'r') as f:
   ....:     content = f.read() 

In [14]: content
Out[14]: '\xe2\x80\x9cThank you.\xe2\x80\x9d'

content是一个str

当您指定有效编码时

In [22]: with codecs.open(filename, 'r', encoding = 'utf-8') as f:
   ....:     content = f.read()


In [23]: content
Out[23]: u'\u201cThank you.\u201d'

content是一个unicode

当您指定无效编码时

In [25]: with codecs.open(filename, 'r', 'ascii') as f:
   ....:     content = f.read()
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

你得到一个UnicodeDecodeError.

于 2013-01-19T15:51:35.233 回答
1

在Python 中有关 Unicode的本教程中,在第 4 段中,它是这样写的,描述了codecs.open(filename, mode, [encoding])您正在使用的函数:

encoding是一个给出要使用的编码的字符串;如果它保留为 None,则返回一个接受 8 位字符串的常规 Python 文件对象。

此外,在对 File object 的引用中,据说

( file.encoding) 也可能是 None,在这种情况下,文件使用系统默认编码来转换 Unicode 字符串。

在没有编码参数的情况下调用codecs.open(),返回一个 File 对象,其编码属性为None(tested),因此使用 Unicode 的系统默认值,在您的情况下必须是 UTF-8。这就解释了为什么当你不明确时它会如此巧妙地工作。

于 2013-01-19T16:01:26.917 回答