Unicode(UTF-8)在Python中读取和写入文件

Unicode (UTF-8) reading and writing to files in Python

我在理解读写文本到一个文件(python 2.4)时脑子有点问题。

1
2
3
4
# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)

("u'Capit\xe1n'","'Capit\xc3\xa1n'")

1
2
3
4
5
6
print ss, ss8
print >> open('f1','w'), ss8

>>> file('f1').read()
'Capit\xc3\xa1n
'

所以我把Capit\xc3\xa1n输入我最喜欢的编辑器,文件F2。

然后:

1
2
3
4
5
6
7
8
9
10
11
12
>>> open('f1').read()
'Capit\xc3\xa1n
'

>>> open('f2').read()
'Capit\\xc3\\xa1n
'

>>> open('f1').read().decode('utf8')
u'Capit\xe1n
'

>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n
'

我在这里不明白什么?很明显,我错过了一些重要的魔法(或者很好的感觉)。在文本文件中键入什么以获得正确的转换?

我在这里真正失败的是,UTF-8表示的意义是什么,如果您不能真正让Python识别它,当它来自外部时。也许我应该只是json转储字符串,然后使用它,因为它有一个可供使用的表示!更重要的是,当python从一个文件中进入时,是否有一个unicode对象的ASCII表示,它将识别并解码?如果有的话,我该怎么办?

1
2
3
4
5
>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'

我发现在打开文件时指定编码更容易,而不是混乱编码和解码方法。io模块(在python 2.6中添加)提供了io.open函数,该函数具有编码参数。

使用io模块中的open方法。

1
2
>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

然后在调用f的read()函数后,返回一个编码的unicode对象。

1
2
3
4
>>>f.read()
u'Capit\xe1l

'

注意,在python 3中,io.open函数是内置open函数的别名。内置的open函数只支持python 3中的编码参数,而不支持python 2。

编辑:以前此答案建议使用编解码器模块。当混合使用read()readline()时,编解码器模块可能会导致问题,因此该答案现在建议使用IO模块。

使用codecs模块中的open方法。

1
2
>>>import codecs
>>>f = codecs.open("test","r","utf-8")

然后在调用f的read()函数后,返回一个编码的unicode对象。

1
2
3
4
>>>f.read()
u'Capit\xe1l

'

如果您知道文件的编码,那么使用codecs包就不会那么令人困惑了。

请参见http://docs.python.org/library/codecs.html codecs.open


在符号中

1
2
u'Capit\xe1n
'

"xe1"只代表一个字节。"x"告诉您"e1"是十六进制的。当你写作时

1
Capit\xc3\xa1n

在您的文件中有"xc3"。这些是4个字节,在您的代码中您可以读取它们。当您显示它们时可以看到:

1
2
3
>>> open('f2').read()
'Capit\\xc3\\xa1n
'

您可以看到反斜杠是由反斜杠转义的。所以字符串中有四个字节:""、"X"、"C"和"3"。

编辑:

正如其他人在他们的答案中指出的那样,您应该只在编辑器中输入字符,然后您的编辑器应该处理到UTF-8的转换并保存它。

如果您有一个这种格式的字符串,您可以使用string_escape编解码器将其解码为普通字符串:

1
2
3
In [15]: print 'Capit\\xc3\\xa1n
'
.decode('string_escape')
Capitán

结果是一个以utf-8编码的字符串,其中重音字符由原始字符串中写入\\xc3\\xa1的两个字节表示。如果你想要一个Unicode字符串,你必须用UTF-8再次解码。

编辑:文件中没有UTF-8。要了解它的实际外观,请执行以下操作:

1
2
3
4
s = u'Capit\xe1n
'

sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

将文件utf-8.out的内容与用编辑器保存的文件的内容进行比较。


现在,你在python3所需要的就是open(Filename, 'r', encoding='utf-8')

【2016-02-10要求澄清的编辑】

python3在其打开函数中添加了编码参数。以下是关于open函数的信息:https://docs.python.org/3/library/functions.html open

1
2
3
open(file, mode='r', buffering=-1,
      encoding=None, errors=None, newline=None,
      closefd=True, opener=None)

Encoding is the name of the encoding used to decode or encode the
file. This should only be used in text mode. The default encoding is
platform dependent (whatever locale.getpreferredencoding()
returns), but any text encoding supported by Python can be used.
See the codecs module for the list of supported encodings.

因此,通过将encoding='utf-8'作为参数添加到open函数中,文件的读写都是以utf8的形式完成的(它现在也是用python完成的所有操作的默认编码)。


因此,我找到了一个解决我所寻找问题的方法,即:

1
print open('f2').read().decode('string-escape').decode("utf-8")

这里有一些非常有用的编解码器。这种特殊的读取允许您从Python中获取UTF-8表示,将它们复制到一个ASCII文件中,并将它们读入Unicode。在"字符串转义"解码下,斜线不会翻倍。

这就允许我想象的那种往返旅行。


事实上,这对于我在python 3.2中读取带有utf-8编码的文件很有用:

1
2
3
4
import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location ="jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()


codecs.open()外,还可以使用io.open()与python2或python3一起工作来读/写unicode文件。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='
'
) as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='
'
) as fin:
    text2 = fin.read()

assert text == text2


要读取Unicode字符串然后发送到HTML,我执行了以下操作:

1
fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

对于支持Python的HTTP服务器很有用。


您已经偶然发现了编码的一般问题:我如何知道文件的编码是什么?

答:除非文件格式有此规定,否则您不能这样做。例如,XML开始于:

1
<?xml encoding="utf-8"?>

这个头是经过精心挑选的,这样无论编码如何,都可以读取它。在您的例子中,没有这样的提示,因此您的编辑器和Python都不知道发生了什么。因此,必须使用codecs模块,并使用codecs.open(path,mode,encoding),它在python中提供缺少的位。

至于编辑器,您必须检查它是否提供了设置文件编码的方法。

UTF-8的要点是能够将21位字符(Unicode)编码为8位数据流(因为这是世界上所有计算机都能处理的唯一事情)。但是,由于大多数OSS早于Unicode时代,它们没有合适的工具将编码信息附加到硬盘上的文件上。

下一个问题是Python中的表示。这一点在Heikogerlach的评论中得到了完美的解释。您必须了解您的控制台只能显示ASCII。为了显示unicode或任何大于等于charcode 128的内容,它必须使用某种转义方法。在编辑器中,不能键入转义的显示字符串,而必须键入该字符串的含义(在本例中,必须输入umlaut并保存文件)。

也就是说,可以使用python函数eval()将转义字符串转换为字符串:

1
2
3
4
5
6
7
8
9
>>> x = eval("'Capit\\xc3\\xa1n\
'"
)
>>> x
'Capit\xc3\xa1n
'

>>> x[5]
'\xc3'
>>> len(x[5])
1

如您所见,字符串"xc3"已转换为单个字符。这是一个8位字符串,用UTF-8编码。获取Unicode:

1
2
3
>>> x.decode('utf-8')
u'Capit\xe1n
'

格雷格·林德问:"我认为这里缺少一些部分:文件F2包含:hex:

1
0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

例如,codecs.open('f2','rb', 'utf-8')以一个单独的字符(预期)读取它们,是否有任何方法可以用ASCII格式写入一个文件?

答案:这取决于你的意思。ASCII不能表示大于127的字符。所以你需要用某种方式说"接下来的几个字符意味着一些特殊的东西",这就是序列"x"所做的。它说:接下来的两个字符是单个字符的代码。""U"使用四个字符对Unicode进行编码,最高为0xffff(65535)。

因此,您不能直接将Unicode写入ASCII(因为ASCII不包含相同的字符)。您可以将其写为字符串转义(如在F2中);在这种情况下,文件可以表示为ASCII。或者您可以将其写为UTF-8,在这种情况下,您需要一个8位的安全流。

使用decode('string-escape')的解决方案确实有效,但您必须知道使用了多少内存:使用codecs.open()的三倍。

记住,文件只是一个8位字节序列。位和字节都没有意义。是你说"65代表‘A’"。由于\xc3\xa1应该变成"_",但是计算机没有办法知道,所以您必须通过指定在写入文件时使用的编码来告诉它。


好吧,您最喜欢的文本编辑器没有意识到\xc3\xa1应该是字符文本,但它将它们解释为文本。这就是为什么在最后一行中有两个反斜杠——它现在是一个真正的反斜杠+xc3等。

如果您想在python中读写编码文件,最好使用codecs模块。

在终端和应用程序之间粘贴文本是困难的,因为您不知道哪个程序将使用哪个编码来解释您的文本。您可以尝试以下操作:

1
2
3
>>> s = file("f1").read()
>>> print unicode(s,"Latin-1")
Capit??n

然后将这个字符串粘贴到编辑器中,并确保它使用拉丁语1存储它。假设剪贴板不会混淆字符串,那么往返应该可以工作。


x.序列是特定于Python的。它不是一个通用的字节转义序列。

您实际如何输入UTF-8编码的非ASCII取决于您的操作系统和/或编辑器。这是你在窗户里做的。要想让OS X输入一个带有尖锐口音的,只需点击option+e,然后点击a,几乎所有OS X中的文本编辑器都支持UTF-8。


您还可以改进原来的open()函数来使用unicode文件,方法是使用partial函数替换它。这个解决方案的好处是您不需要更改任何旧代码。它是透明的。

1
2
3
import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')


我试图用python 2.7.9解析iCal:

from icalendar import Calendar

但我得到了:

1
2
3
4
 Traceback (most recent call last):
 File"ical.py", line 92, in parse
    print"{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

它被修复为:

1
print"{}".format(e[attr].encode("utf-8"))

(现在它可以像"B"一样打印了吗?SS)