python - 使用 UTF-8 字符串写入文件期间的 Python 编解码器错误
问题描述
我正在开发一个 Python 3 Tkinter 应用程序(操作系统为 Windows 10),其整体功能包括以下详细信息:
读取许多可能包含 ascii、cp1252、utf-8 或任何其他编码的数据的文本文件
在“预览窗口”(Tkinter 标签小部件)中显示任何这些文件的内容。
将文件内容写入单个输出文件(每次打开以追加)
对于#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()
解决方案
我知道你想要两件事:
- 一种将任意 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。
推荐阅读
- python - 为什么 Alpha 混合无法正常工作?
- android - 如何在运行时将 ViewGroup 分配给另一个 ViewGroup
- javascript - Angular - 在所有路线的末尾添加自定义路线
- java - Mockito 没有返回预期的字符串,而是返回了模拟对象
- android - 呼叫需要权限,可能会被用户拒绝:
- swift - 使用 RxSwift 重放最后一个请求
- security - 移动应用程序的安全漏洞
- php - PHP HTTP_HOST 可以返回值 'localhost' 但 REMOTE_ADDR 是公共 ip 吗?
- aws-sdk - 如何将记录从 NodeJS 应用程序 (lambda) 放入 Kinesis 流
- linux - 如何在用户空间获取/设置 MAX_ZONEORDER 的值?