使用UTF-8字符串写入文件时发生Python编解码器错误

时间:2019-02-22 21:10:22

标签: python python-3.x python-2.7 utf-8 file-writing

我正在使用Python 3 Tkinter应用程序(操作系统为Windows 10),其总体功能包括以下细节:

  1. 读取许多文本文件,其中可能包含ascii,cp1252,utf-8或任何其他编码的数据

  2. 在“预览窗口”(Tkinter标签窗口小部件)中显示所有这些文件的内容。

  3. 将文件内容写入单个输出文件(每次打开都将追加)

对于#1:我仅通过以二进制模式打开和读取文件就使文件读取与编码无关。要将数据转换为字符串,我使用了一个循环,循环遍历“可能”的编码列表,并依次尝试使用error='strict'编码,直到遇到不会引发异常的编码为止。工作正常。

对于#2:获取解码的字符串后,我只需为Tkinter Label的set()调用textvariable方法即可。这也可以。

对于#3:我将以通常的方式打开输出文件,并调用write()方法来编写解码后的字符串。当字符串被解码为ascii或cp1252时,此方法有效,但是当其被解码为utf-8时,它将引发异常:

'charmap' codec can't encode characters in position 0-3: character maps to <undefined>

我四处搜寻,发现了相当相似的问题,但似乎没有解决这个特定问题的方法。进一步的复杂性限制了适用于我的解决方案:

A。我可以通过将读取的数据保留为字节并以二进制形式打开/写入输出文件来避免该问题,但这会使一些输入文件的内容不可读。

B。尽管此应用程序主要用于Python 3,但我正在尝试使其与Python 2交叉兼容-我们有一些使用缓慢/延迟的使用者。 (顺便说一句,当我在Python 2上运行该应用程序时,它也会引发异常,但同时对cp1252数据和utf-8数据都会引发异常。)


为了说明问题,我创建了这个简化的测试脚本。 (我的实际应用程序是一个更大的项目,它也是我公司的专有-因此不会公开发布!)

import tkinter as tk
import codecs

#Root window
root = tk.Tk()

#Widgets
ctrlViewFile1 = tk.StringVar()
ctrlViewFile2 = tk.StringVar()
ctrlViewFile3 = tk.StringVar()
lblViewFile1 = tk.Label(root, relief=tk.SUNKEN,
                        justify=tk.LEFT, anchor=tk.NW,
                        width=10, height=3,
                        textvariable=ctrlViewFile1)
lblViewFile2 = tk.Label(root, relief=tk.SUNKEN,
                        justify=tk.LEFT, anchor=tk.NW,
                        width=10, height=3,
                        textvariable=ctrlViewFile2)
lblViewFile3  = tk.Label(root, relief=tk.SUNKEN,
                         justify=tk.LEFT, anchor=tk.NW,
                         width=10, height=3,
                         textvariable=ctrlViewFile3)

#Layout
lblViewFile1.grid(row=0,column=0,padx=5,pady=5)
lblViewFile2.grid(row=1,column=0,padx=5,pady=5)
lblViewFile3.grid(row=2,column=0,padx=5,pady=5)

#Bytes read from "files" (ascii Az5, cp1252 European letters/punctuation, utf-8 Mandarin characters)
inBytes1 = b'\x41\x7a\x35'
inBytes2 = b'\xe0\xbf\xf6'
inBytes3 = b'\xef\xbb\xbf\xe6\x9c\xa8\xe5\x85\xb0\xe8\xbe\x9e'

#Decode
outString1 = codecs.decode(inBytes1,'ascii','strict')
outString2 = codecs.decode(inBytes2,'cp1252','strict')
outString3 = codecs.decode(inBytes3,'utf_8','strict')

#Assign stringvars
ctrlViewFile1.set(outString1)
ctrlViewFile2.set(outString2)
ctrlViewFile3.set(outString3)

#Write output files
try:
    with open('out1.txt','w') as outFile:
        outFile.write(outString1)
except Exception as e:
    print(inBytes1)
    print(str(e))

try:
    with open('out2.txt','w') as outFile:
        outFile.write(outString2)
except Exception as e:
    print(inBytes2)
    print(str(e))

try:
    with open('out3.txt','w') as outFile:
        outFile.write(outString3)
except Exception as e:
    print(inBytes3)
    print(str(e))

#Start GUI
tk.mainloop()

2 个答案:

答案 0 :(得分:2)

我了解您想要两件事:

  • 一种将任意Unicode字符写入文件的方法,
  • Python 2/3兼容性。

使用open('out1.txt','w')违反了这两项:

  • 将使用默认编码打开输出文本流,默认编码在您的平台上(显然是Windows)为CP-1252。该编解码器仅支持Unicode的子集,例如。缺少所有表情符号。
  • open函数在Python版本之间存在很大差异。在Python 3中,它是io.open函数,它提供了很多灵活性,例如指定文本编码。在Python 2中,返回的文件句柄处理8位字符串而不是Unicode字符串(文本)。
  • 还有一个您可能不知道的可移植性问题:IO的默认编码取决于平台,即。运行您的代码的人可能会看到不同的默认值,具体取决于操作系统和本地化。

您可以使用io.open('out1.txt', 'w', encoding='utf8')避免所有这些事情:

  • 使用支持所有所需字符的编码。除非处理过程中引入的字符超出了支持范围,否则使用检测到的输入编码应该可以。使用其中一种UTF编解码器将始终有效,而UTF-8是最广泛用于文本文件的代码。请注意,某些Windows应用程序(例如记事本)往往不了解UTF-8。
  • io模块已反向移植到Python 2.7。由于支持<= 2.6的版本已在相当一段时间前结束,因此,这通常可以与Py2 / 3兼容。
  • 明确打开文本文件时使用的编码。在某些情况下,依赖平台的默认编码是有意义的,但是通常您需要控制。

旁注: 您提到了一种用于检测输入编解码器的简单启发式方法。 如果确实无法获取此信息,则应考虑使用chardet

答案 1 :(得分:0)

要明确。您已打开使用默认编码进行写入的权限。无论是什么,它都不支持所有Unicode代码点。使用UTF-8编码打开文件,该编码确实支持所有Unicode代码点:

import io
with io.open('out3.txt','w',encoding='utf8') as outFile: