Python不能正确解释UTF8

时间:2013-05-22 00:41:16

标签: python django unicode utf-8

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

我有一个django应用程序,我在其中创建了一个管理脚本。这个脚本读取一些文本文件,然后将它们输出到终端(稍后会对内容做更多有用的东西,但我还在测试它),并且字符出现了像\xc3\xa5这样的转义序列而不是预期的å。由于该转义序列意味着Ã¥,这是å由于编码问题而常见的误解,我怀疑至少有两个地方出错了。但是,我无法弄清楚在哪里 - 我已经检查了所有可能的罪魁祸首:

  • 终端编码为UTF-8; echo $LANG提供en_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更改为.../python或者完全将其删除以将其留给Django,但没有结果。
  • 根据the documentation,“Django本身支持Unicode数据”,所以我“可以安全地在应用程序的任何地方传递Unicode字符串。”

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

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

更新

回应评论中的静止:

  • 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

3 个答案:

答案 0 :(得分:2)

这里的一个大问题是你正在混合使用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_之类的包装,或者执行{ {1}},或者做一些丑陋的事情,例如添加额外的括号以确保你的元组在两个版本中都是元组。


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

为什么呢?好吧,在任一版本中打印任何内容,只需在其上调用str,然后将其传递给str。但在3.x中,sys.stdio.write表示strunicode表示sys.stdio;在2.x中,TextIOWrapper表示strbytes是二进制sys.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.TextIOWrapper然后使用codecs.StreamWriterprint >>f, …代替f.write(…)来模拟Python 3,或者您可以显式编码所有print个对象都是这样的:

unicode

但实际上,处理所有这些问题的最佳方法是在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版本也可以提供帮助。

答案 1 :(得分:0)

  

为了完整性:我正在使用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

......我得到了你所看到的行为。

答案 2 :(得分:0)

从您的评论中可以看出,您使用的是python-2而不是python-3。

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

编码的基本流程为:

从编码到unicode的DECODE - >处理 - >从unicode编码到编码

在python3中,字节解码到字符串,字符串编码到字节。 使用open()处理字符串解码的字节。

  

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

因此,要从 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