Py学习  »  Python

使用utf-8字符串写入文件时出现python编解码器错误

JDM • 6 年前 • 1853 次点击  

我正在开发一个python 3 tkinter应用程序(os是windows 10),其总体功能包括以下详细信息:

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

  2. 在“预览窗口”(tkinter label小部件)中显示这些文件的内容。

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

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

for 2:一旦我得到解码的字符串,我就调用 set() Tkinter标签的方法 textvariable . 这也是可行的。

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

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

我四处搜索,发现了一些类似的问题,但似乎没有解决这个具体问题。一些更复杂的问题限制了我的解决方案:

a.我可以回避这个问题,只需将读入的数据保留为字节,并将输出文件作为二进制文件打开/写入,但这会使一些输入文件内容无法读取。

B.虽然这个应用程序主要是针对Python3的,但我正在尝试使它与Python2交叉兼容——我们有一些慢/晚的采用者将使用它。(顺便说一下,当我在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()
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/44113
文章 [ 2 ]  |  最新文章 6 年前
Mark Tolonen
Reply   •   1 楼
Mark Tolonen    6 年前

要明确。您已使用默认编码打开进行写入。不管是什么,它都不支持所有的unicode代码点。用utf-8编码打开文件,其中 支持所有Unicode代码点:

import io
with io.open('out3.txt','w',encoding='utf8') as outFile:
lenz
Reply   •   2 楼
lenz    6 年前

我知道你想要两样东西:

  • 一种将任意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 模块被移植到Python2.7。这通常符合py2/3兼容,因为对版本<=2.6的支持在相当长一段时间前就已经结束了。
  • 打开文本文件时,请明确使用的编码。在某些情况下,依赖于平台的默认编码是有意义的,但通常需要控制。

边注: 您提到了一个检测输入编解码器的简单启发式方法。 如果实在无法获得这些信息,您应该考虑使用 chardet .