首页 > 解决方案 > 使用 UTF-8 字符串写入文件期间的 Python 编解码器错误

问题描述

我正在开发一个 Python 3 Tkinter 应用程序(操作系统为 Windows 10),其整体功能包括以下详细信息:

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

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

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

对于#1:我通过以二进制模式打开和读取文件,使文件读取编码不可知。要将数据转换为字符串,我使用了一个循环,该循环遍历“可能”编码的列表,并依次尝试它们中的每一个(使用error='strict'),直到它遇到一个不会引发异常的编码。这是有效的。

对于#2:一旦我得到解码的字符串,我只需调用set()Tkinter 标签的方法textvariable。这也有效。

对于#3:我以通常的方式打开一个输出文件并调用该write()方法来写入解码的字符串。这在字符串被解码为 ascii 或 cp1252 时有效,但当它被解码为 utf-8 时,它会引发异常:

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

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

答:我可以通过将读入数据保留为字节并将输出文件打开/写入为二进制文件来回避该问题,但这会导致某些输入文件内容不可读。

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()

标签: pythonpython-3.xpython-2.7utf-8file-writing

解决方案


我知道你想要两件事:

  • 一种将任意 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。这通常与 Py2/3 兼容,因为对版本 <= 2.6 的支持已经结束了很久。
  • 明确说明打开文本文件时使用的编码。在某些情况下,依赖于平台的默认编码是有意义的,但通常您需要控制。

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


推荐阅读