【问题标题】:Python doesn't interpret UTF8 correctlyPython 不能正确解释 UTF8
【发布时间】:2013-05-16 21:52:19
【问题描述】:

我知道类似的问题已经被问过一百万次了,但是尽管阅读了其中很多,我还是找不到适合我情况的解决方案。

我有一个 django 应用程序,我在其中创建了一个管理脚本。该脚本读取一些文本文件,并将它们输出到终端(稍后它将对内容做更多有用的事情,但我仍在对其进行测试)并且字符以\xc3\xa5 之类的转义序列出现,而不是预期的å。由于该转义序列表示Ã¥,这是由于编码问题而对å 的常见误解,我怀疑至少有两个地方出错了。但是,我不知道在哪里 - 我已经检查了我能想到的所有可能的罪魁祸首:

  • 终端编码为UTF-8; echo $LANGen_US.UTF-8
  • 文本文件以 UTF-8 编码; file * 在它们所在的目录中导致所有条目都被列为“UTF-8 Unicode 文本”,除了一个不包含任何非 ASCII 字符并被列为“ASCII 文本”的条目。在该文件上运行 iconv -f ascii -t utf8 thefile.txt > utf8.txt 会生成另一个具有 ASCII 文本编码的文件。
  • Python 脚本都是 UTF-8(或者,在某些情况下,是没有非 ASCII 字符的 ASCII)。我尝试在我的管理脚本中插入带有一些特殊字符的注释以强制它保存为 UTF-8,但它并没有改变行为。以上对文本文件的观察也适用于所有 Python 脚本文件。
  • 处理文本文件的 Python 脚本顶部有 # -*- encoding: utf-8 -*-;前面唯一的一行是 #!/usr/bin/python3,但我尝试将 Python 2.7 更改为 .../python 或将其完全删除以将其留给 Django,但没有结果。
  • 根据the documentation,“Django 原生支持 Unicode 数据”,因此我“可以在应用程序的任何位置安全地传递 Unicode 字符串”。

我真的想不出任何其他地方可以在链中寻找非 UTF-8 链接。我在哪里可能错过了更改为 UTF-8 的设置?

为了完整起见:我正在使用 lines = file.readlines() 读取文件并使用标准 print() 函数进行打印。两端都没有手动编码或解码。

更新:

针对cmets的提问:

  • print(sys.getdefaultencoding(), sys.stdout.encoding, f.encoding) 为所有文件生成 ('ascii', 'UTF-8', None)
  • 我开始编译一个 SSCCE,很快发现只有在尝试打印元组中的值时才会出现问题。换句话说,print(lines[0].strip()) 工作正常,但print(lines[0].strip(), lines[1].strip()) 不行。添加.decode('utf-8') 会生成一个元组,其中两个字符串都标有前面的u\xe5å 的正确转义序列)而不是之前的奇数字符 - 但我不知道如何打印它们作为常规字符串,没有转义字符。我已经测试了对.decode('utf-8') 的另一个调用以及在str() 中的包装,但是两者都失败了UnicodeEncodeError 抱怨\xe5 不能用ascii 编码。由于单个字符串可以正常工作,我不知道还要测试什么。

SSCCE:

# -*- coding: utf-8 -*-

import os, sys

for root,dirs,files in os.walk('txt-songs'):
    for filename in files:
        with open(os.path.join(root,filename)) as f:
            print(sys.getdefaultencoding(), sys.stdout.encoding, f.encoding)

            lines = f.readlines()
            print(lines[0].strip()) # works
            print(lines[0].strip(), lines[1].strip()) # does not work

【问题讨论】:

  • 是的,Django 原生支持 Unicode,你可以传递 Unicode 字符串……但最终,如果你正在读/写文件或 stdin/stdout,你必须在边缘进行编码/解码。
  • 解码:'\xc3\xa5'.decode('utf-8') == u'\xe5'
  • 无论如何,我的第一个建议是给我们一个SSCCE,这样我们就可以在你的代码中找到实际的错误,而不是仅仅试图猜测你可能做错了什么。其次,为每个文本模式文件(在 3.x 中)打印出 sys.getdefaultencoding()sys.stdout.encodingf.encoding
  • 一种可能的可能性是您将 Unicode 正确编码为 UTF-8,然后将该 UTF-8 打印为 Latin-1/CP819/等。 stdout。 (Blender 的编辑评论显示他比我早 4 分钟就在想同样的事情……)
  • 您必须回答我们为调试问题而提供的所有问题,而不是部分回答少数问题,否则我们无法为您调试问题。

标签: python django unicode utf-8


【解决方案1】:

这里最大的问题是您将 Python 2 和 Python 3 混合在一起。特别是,您编写了 Python 3 代码,并且尝试在 Python 2.7 中运行它。但在此过程中还有一些其他问题。所以,让我试着解释一下出了什么问题。


我开始编译一个 SSCCE,并很快发现只有在尝试打印元组中的值时才会出现问题。换句话说,print(lines[0].strip()) 工作正常,但 print(lines[0].strip(), lines[1].strip()) 不行。

这里的第一个问题是元组(或任何其他集合)的str 包含其元素的repr,而不是str。解决此问题的简单方法是不打印集合。在这种情况下,根本没有理由打印一个元组。您拥有它的唯一原因是您已经为打印而构建了它。只需执行以下操作:

print '({}, {})'.format(lines[0].strip(), lines[1].strip())

如果你已经在一个变量中有一个集合,并且你想打印出每个元素的 str,你必须明确地这样做。您可以使用以下命令打印每个 str 的 repr:

print tuple(map(str, my_tuple))

... 或直接打印每个 str 的:

print '({})'.format(', '.join(map(str, my_tuple)))

请注意,我使用的是上面的 Python 2 语法。那是因为如果你真的使用 Python 3,一开始就没有元组,也不需要调用str


你有一个 Unicode 字符串。在 Python 3 中,unicodestr 是同一类型。但是在 Python 2 中,bytesstr 是同一类型,unicode 是不同的类型。因此,在 2.x 中,您还没有 str,这就是您需要调用 str 的原因。

Python 2 也是 print(lines[0].strip(), lines[1].strip()) 打印元组的原因。在 Python 3 中,这是使用两个字符串作为参数调用 print 函数,因此它将打印出两个用空格分隔的字符串。在 Python 2 中,它是一个带有一个参数的 print 语句,它是一个元组。

如果您想编写在 2.x 和 3.x 中工作相同的代码,您要么需要避免打印多个参数,要么使用像 six.print_ 这样的包装器,或者执行 from __future__ import print_function , 或者要非常小心地做一些丑陋的事情,例如添加额外的括号以确保您的元组在两个版本中都是元组。


因此,在 3.x 中,您有 str 对象,您只需将它们打印出来。在 2.x 中,您有 unicode 对象,并且您正在打印出它们的 repr。您可以将其更改为打印出他们的str,或者首先避免打印元组……但这仍然无济于事。

为什么?好吧,在任何一个版本中打印任何东西,只需在上面调用str,然后将其传递给sys.stdio.write。但在 3.x 中,str 表示unicode,而sys.stdioTextIOWrapper;在 2.x 中,str 表示 bytessys.stdio 是二进制 file

所以,最终发生的伪代码是:

sys.stdio.wrapped_binary_file.write(s.encode(sys.stdio.encoding, sys.stdio.errors))

sys.stdio.write(s.encode(sys.getdefaultencoding()))

而且,正如您所见,它们会做不同的事情,因为:

print(sys.getdefaultencoding(), sys.stdout.encoding, f.encoding) 产生('ascii', 'UTF-8', None)

您可以在此处使用io.TextIOWrappercodecs.StreamWriter 模拟Python 3,然后使用print >>f, …f.write(…) 而不是print,或者您可以像这样显式编码所有unicode 对象:

print '({})'.format(', '.join(element.encode('utf-8') for element in my_tuple)))

但实际上,处理所有这些问题的最佳方法是在 Python 3 解释器而不是 Python 2 解释器中运行现有的 Python 3 代码。

如果您想要或需要使用 Python 2.7,那很好,但您必须编写 Python 2 代码。如果您想编写 Python 3 代码,那很好,但您必须运行 Python 3.3。如果你真的想编写在这两种情况下都能正常工作的代码,你可以,但这是额外的工作,需要更多的知识。

有关更多详细信息,请参阅What's New In Python 3.0(“打印是一个函数”和“文本与数据而不是 Unicode 与 8 位”部分),尽管这是从解释 3.x 的角度编写的到 2.x 用户,这与您的需要相反。 Unicode HOWTO 的3.x2.x 版本也可能有所帮助。

【讨论】:

  • 这非常有启发性,并且发现了问题。然而,由于我更喜欢​​ Python 3.x 而不是 Python 2.7,所以我决定只做from __future__ import print_function, unicode_literals,它勇敢地解决了这个问题。感谢您的详尽回答!
  • @TomasLycken:我也更喜欢 Python 3.x 而不是 Python 2.7。那么……为什么不直接使用 Python 3.3? Django 与 3.3 兼容,而且您使用的任何其他库也是不错的选择。当然,可能您会遇到一个尚未移植的所需库……但不要只是假设,试试看吧。
  • Django(显然)在 Ubuntu 中默认安装了 python 2.7,我没有费心去改变它。毕竟,这是我安装 Django 后做的第一件事(这次——但上次是几年前,还有几个 Ubuntu 版本……)。
  • @TomasLycken:是的,看起来 Ubuntu 不打算提供 python3-django 包,直到他们将默认的 Django 版本升级到 1.6。所以,你可以安装一个非官方的包,pip-3.3 install django,或者编写 Python 2.7 代码。
【解决方案2】:

为了完整起见:我正在使用lines = file.readlines() 从文件中读取并使用标准的print() 函数进行打印。两端都没有手动编码或解码。

在 Python 3.x 中,标准的 print 函数只是将 Unicode 写入 sys.stdout。既然是io.TextIOWrapper,那么它的write方法就等价于:

self.wrapped_binary_file.write(s.encode(self.encoding, self.errors))

所以一个可能的问题是sys.stdout.encoding 与您终端的实际编码不匹配。


当然还有一个原因是您的 shell 的编码与终端窗口的编码不匹配。

例如,在 OS X 上,我创建一个 myscript.py,如下所示:

print('\u00e5')

然后我启动 Terminal.app,创建一个编码为“Western (ISO Latin 1)”的会话配置文件,使用该会话配置文件创建一个选项卡,然后执行以下操作:

$ export LANG=en_US.UTF-8
$ python3 myscript.py

…我完全理解你所看到的行为。

【讨论】:

  • 如何在 Ubuntu 13.04 上最终检查我的 shell 和终端编码? echo $LANG 似乎是我能找到的被提及最多的方法,但从您的回答看来,这可能会产生误导。
  • @TomasLycken:嗯,echo $LANG 是检查您的 shell 的方法(尽管您可能想要运行 locale 并确保所有设置都一致),但这并没有告诉你任何关于你的终端的信息。当然,如果一切正常,它们会匹配......但重点是我们怀疑并非一切正常,所以...... Ubuntu 仍然使用gnome-terminal 吗?它应该有菜单选项可以让你改变编码……但默认应该是从你的环境中读取。如果您将个人资料中的区域设置更改为与登录时不同,这可能会出错。
  • @TomasLycken:对不起,我不能给出关于 Ubuntu 的更具体的答案,但真的 askubuntu、linuxquestions、超级用户等可能是更好的去处。
【解决方案3】:

从您的评论看来,您使用的是 python-2 而不是 python-3。

如果您使用的是 python-3,则值得阅读 reading/writing 上的 unicode howto guide 以了解 python 在做什么。

如果编码的基本流程是:

从编码解码到 unicode -> 处理 -> 从 unicode 编码到编码

在python3中,字节解码为字符串,字符串编码为字节。 字符串解码的字节由open()处理。

[..] 内置的 open() 函数可以返回一个类似文件的对象 假定文件的内容采用指定的编码并接受 read() 和 write() 等方法的 Unicode 参数。这有效 通过 open() 的 encoding 和错误参数 [..]

因此,要从 utf-8 编码 文件中读取 unicode,您应该这样做:

# python-3
with open('utf8.txt', mode='r', encoding='utf-8') as f:
    lines = f.readlines() # returns unicode 

如果你想使用 python-2 实现类似的功能,你可以使用codecs.open():

# python-2
import codecs
with codecs.open('utf8.txt', mode='r', encoding='utf-8') as f:
    lines = f.readlines() # returns unicode 

【讨论】:

  • 这给了我TypeError: 'encoding' is an invalid keyword argument for this function。仅更改为 codingenc 并没有帮助 - 我的印象是 Python 无论如何都会这样做,因为其他所有内容都是用 Unicode 指定的。
  • 好的,这意味着您使用的是 python 2。我从您的问题中得到的印象是您使用的是 python 3。
猜你喜欢
  • 2018-09-19
  • 2020-10-23
  • 2012-06-12
  • 1970-01-01
  • 2011-06-14
  • 2017-09-04
  • 2022-01-11
  • 2018-12-10
  • 1970-01-01
相关资源
最近更新 更多