簡體   English   中英

使用 UTF-8 字符串寫入文件時出現 Python 編解碼器錯誤

[英]Python codec error during file write with UTF-8 string

我正在開發一個 Python 3 Tkinter 應用程序(操作系統是 Windows 10),其整體功能包括以下詳細信息:

  1. 讀取可能包含 ascii、cp1252、utf-8 或任何其他編碼格式數據的多個文本文件

  2. 在“預覽窗口”(Tkinter 標簽小部件)中顯示任何這些文件的內容。

  3. 將文件內容寫入單個輸出文件(每次打開以追加)

對於#1:我只是通過以二進制模式打開和讀取文件來使文件讀取編碼不可知。 要將數據轉換為字符串,我使用了一個循環,該循環遍歷“可能”編碼列表並依次嘗試每個編碼(使用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>

我四處搜索並發現了相當相似的問題,但似乎沒有解決這個特定問題的問題。 一些進一步的復雜性限制了對我有用的解決方案:

答:我可以通過將讀入數據保留為字節並將輸出文件作為二進制文件打開/寫入來回避這個問題,但這會導致某些輸入文件內容不可讀。

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 的一個子集,例如。 缺少所有表情符號。
  • Python 版本之間的open函數差異很大。 在 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

明確一點。 您已使用默認編碼打開寫入。 不管它是什么,它都不支持所有的 Unicode 代碼點。 打開與UTF-8編碼,它支持所有Unicode代碼點的文件:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM