Python 使用 UTF-8 字符串写入文件时出现编解码器错误
Python codec error during file write with UTF-8 string
我正在开发 Python 3 Tkinter 应用程序(OS 是 Windows 10),其整体功能包括以下细节:
正在读取大量可能包含 ascii、cp1252、utf-8 或任何其他编码格式数据的文本文件
在 "preview window"(Tkinter 标签小部件)中显示任何这些文件的内容。
将文件内容写入单个输出文件(每次打开追加)
对于 #1:我通过以二进制模式打开和读取文件,使文件读取与编码无关。为了将数据转换为字符串,我使用了一个循环,该循环 运行 遍历 'likely' 编码列表,并依次尝试每个编码(使用 error='strict'
),直到它遇到一个不存在的'不抛出异常。这是有效的。
对于 #2:获得解码后的字符串后,我只需为 Tkinter 标签的 textvariable
调用 set()
方法。这也有效。
对于 #3:我以通常的方式打开输出文件并调用 write()
方法来写入解码后的字符串。这在字符串被解码为 ascii 或 cp1252 时有效,但当它被解码为 utf-8 时它会抛出异常:
'charmap' codec can't encode characters in position 0-3: character maps to <undefined>
我四处搜索并发现了非常相似的问题,但似乎没有任何问题可以解决这个特定问题。一些进一步的并发症限制了对我有用的解决方案:
一个。我可以通过将读入数据保留为字节和 opening/writing 将输出文件保留为二进制来回避问题,但这会导致一些输入文件内容不可读。
乙。虽然此应用程序主要用于 Python 3,但我正在尝试使其与 Python 2 交叉兼容——我们有一些 slow/late 用户将使用它。 (顺便说一句,当我 运行 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 代码点。使用 UTF-8 编码打开文件,确实支持所有 Unicode 代码点:
import io
with io.open('out3.txt','w',encoding='utf8') as outFile:
我知道你想要两件事:
- 一种将任意 Unicode 字符写入文件的方法,并且
- Python 2/3 兼容性。
使用 open('out1.txt','w')
违反了两者:
- 输出文本流以默认编码打开,在您的平台上恰好是 CP-1252(显然 Windows)。此编解码器仅支持 Unicode 的一个子集,例如。缺少所有表情符号。
open
函数在 Python 版本之间有很大差异。在 Python 3 中,是 io.open
函数,它提供了很多灵活性,例如指定文本编码。在Python2中,返回的文件句柄处理8位字符串而不是Unicode字符串(文本)。
- 还有一个您可能不知道的可移植性问题:IO 的默认编码是平台相关的,即。 people 运行 您的代码可能会看到不同的默认值,具体取决于 OS 和本地化。
您可以使用 io.open('out1.txt', 'w', encoding='utf8')
来避免这一切:
- 使用支持所有所需字符的编码。使用检测到的输入编码应该有效,除非处理引入了支持范围之外的字符。使用其中一种 UTF 编解码器将始终有效,其中 UTF-8 最广泛地用于文本文件。请注意,某些 Windows 应用程序(如记事本)往往不理解 UTF-8。
utf-8-sig
编解码器支持编写带 BOM 的 UTF-8,这使得 Windows 应用程序可以识别以 UTF-8 编码的文件。该编解码器还从输入流中删除 UTF-8 BOM 签名(如果在用于读取时存在)。
io
模块被移植到 Python 2.7。这通常符合 Py2/3 兼容,因为对版本 <= 2.6 的支持在很久以前就已经结束了。
- 明确说明打开文本文件时使用的编码。可能存在依赖于平台的默认编码有意义的场景,但通常您需要控制。
旁注:
您提到了一种用于检测输入编解码器的简单启发式方法。
如果真的没有办法获取这些信息,你应该考虑使用chardet.
我正在开发 Python 3 Tkinter 应用程序(OS 是 Windows 10),其整体功能包括以下细节:
正在读取大量可能包含 ascii、cp1252、utf-8 或任何其他编码格式数据的文本文件
在 "preview window"(Tkinter 标签小部件)中显示任何这些文件的内容。
将文件内容写入单个输出文件(每次打开追加)
对于 #1:我通过以二进制模式打开和读取文件,使文件读取与编码无关。为了将数据转换为字符串,我使用了一个循环,该循环 运行 遍历 'likely' 编码列表,并依次尝试每个编码(使用 error='strict'
),直到它遇到一个不存在的'不抛出异常。这是有效的。
对于 #2:获得解码后的字符串后,我只需为 Tkinter 标签的 textvariable
调用 set()
方法。这也有效。
对于 #3:我以通常的方式打开输出文件并调用 write()
方法来写入解码后的字符串。这在字符串被解码为 ascii 或 cp1252 时有效,但当它被解码为 utf-8 时它会抛出异常:
'charmap' codec can't encode characters in position 0-3: character maps to <undefined>
我四处搜索并发现了非常相似的问题,但似乎没有任何问题可以解决这个特定问题。一些进一步的并发症限制了对我有用的解决方案:
一个。我可以通过将读入数据保留为字节和 opening/writing 将输出文件保留为二进制来回避问题,但这会导致一些输入文件内容不可读。
乙。虽然此应用程序主要用于 Python 3,但我正在尝试使其与 Python 2 交叉兼容——我们有一些 slow/late 用户将使用它。 (顺便说一句,当我 运行 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 代码点。使用 UTF-8 编码打开文件,确实支持所有 Unicode 代码点:
import io
with io.open('out3.txt','w',encoding='utf8') as outFile:
我知道你想要两件事:
- 一种将任意 Unicode 字符写入文件的方法,并且
- Python 2/3 兼容性。
使用 open('out1.txt','w')
违反了两者:
- 输出文本流以默认编码打开,在您的平台上恰好是 CP-1252(显然 Windows)。此编解码器仅支持 Unicode 的一个子集,例如。缺少所有表情符号。
open
函数在 Python 版本之间有很大差异。在 Python 3 中,是io.open
函数,它提供了很多灵活性,例如指定文本编码。在Python2中,返回的文件句柄处理8位字符串而不是Unicode字符串(文本)。- 还有一个您可能不知道的可移植性问题:IO 的默认编码是平台相关的,即。 people 运行 您的代码可能会看到不同的默认值,具体取决于 OS 和本地化。
您可以使用 io.open('out1.txt', 'w', encoding='utf8')
来避免这一切:
- 使用支持所有所需字符的编码。使用检测到的输入编码应该有效,除非处理引入了支持范围之外的字符。使用其中一种 UTF 编解码器将始终有效,其中 UTF-8 最广泛地用于文本文件。请注意,某些 Windows 应用程序(如记事本)往往不理解 UTF-8。
utf-8-sig
编解码器支持编写带 BOM 的 UTF-8,这使得 Windows 应用程序可以识别以 UTF-8 编码的文件。该编解码器还从输入流中删除 UTF-8 BOM 签名(如果在用于读取时存在)。 io
模块被移植到 Python 2.7。这通常符合 Py2/3 兼容,因为对版本 <= 2.6 的支持在很久以前就已经结束了。- 明确说明打开文本文件时使用的编码。可能存在依赖于平台的默认编码有意义的场景,但通常您需要控制。
旁注: 您提到了一种用于检测输入编解码器的简单启发式方法。 如果真的没有办法获取这些信息,你应该考虑使用chardet.