【问题标题】:Python 2.7.1, codecs, and MySQLdb; why is setting file encoding not neededPython 2.7.1、编解码器和 MySQLdb;为什么不需要设置文件编码
【发布时间】:2012-12-31 07:41:03
【问题描述】:

我有一个小的 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 通常都不错。这里和博客中有大量关于为什么需要设置编码以及如何设置编码的帖子,但我没有找到任何关于它为什么有时会起作用的信息。

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

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”

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

【问题讨论】:

  • 除了将文件内容提供给 MySQL 之外,您还对文件的内容做任何事情吗? Python 可以正常读取 UTF8 格式的旧文件。根据我的经验,当您尝试将其写回时,通常会出现通常的“latin-1 编解码器无法编码”错误。
  • 我将生成的数据库提供给假设它是 UTF8 的 WordPress。当它正常工作时,文本会正确显示,当它不正常时,文本会显示很多奇怪的字符。 “用常规的旧打开方式阅读它就很好”让我感到困惑,因为我认为默认编码是 ISO 8859-1。
  • @anov,谢谢,我在问题中添加了“作品”的定义。

标签: python unicode python-2.7 mysql-python codec


【解决方案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

特别注意如果没有设置encoding 会发生什么:

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

所以codecs.open 在没有设置编码时与内置的open 基本相同。内置的open 返回一个文件对象,其read 方法返回一个str 对象。 它根本不解码。

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

在您的示例中,str 对象是

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

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

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'

contentstr

当您指定有效编码时

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


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

contentunicode

当您指定无效编码时

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

【讨论】:

  • 这是否意味着如果我知道编码,我应该始终使用 codecs.open?
  • 确实,还是明确一点!
  • 我已经更新了我的问题。没有指定时,似乎没有进行编码。
  • 这解释了文件读取的情况。谢谢。
【解决方案2】:

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

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

另外,在reference on the File object中,据说

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

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

【讨论】:

  • 如何确定系统默认编码?是否使用 open 等同于使用 codecs.open 而不指定编码?
  • 当我使用内置的 open() 时它是如何工作的?我已编辑问题以添加此内容。
  • 是的,使用open()返回一个编码属性为NoneFile对象,与不带编码参数的codecs.open相同。你可以通过sys.getdefaultencoding() 找出你的系统默认编码。要更改它,请参阅stackoverflow.com/questions/2276200/…
  • 谢谢,我在 Mac 上,sys.getdefaultencoding() 返回 ascii。所以它为什么起作用并不明显。
  • 我明白了。您能否从您的代码中提供更多信息,例如将fh 的内容发送到数据库的行?
猜你喜欢
  • 1970-01-01
  • 2020-01-30
  • 2021-10-31
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多