Ошибка кодека Python во время записи файла со строкой UTF-8 - PullRequest
0 голосов
/ 23 февраля 2019

Я работаю над приложением Python 3 Tkinter (ОС - Windows 10), общая функциональность которого включает в себя следующие детали:

  1. Чтение ряда текстовых файлов, которые могут содержать данные вascii, cp1252, utf-8 или любая другая кодировка

  2. Отображение содержимого любого из этих файлов в «окне предварительного просмотра» (виджет Tkinter Label).

  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>

Я искал вокруг и нашел довольно похожие вопросы, но ничего, что, кажется, не решаетэто конкретная проблема.Некоторые дополнительные сложности, которые ограничивают решения, которые будут работать для меня:

A.Я могу обойти эту проблему, просто оставив считанные данные в виде байтов и открыв / записав выходной файл в двоичном виде, но это делает некоторые из содержимого входного файла нечитаемыми.

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

Ответы [ 2 ]

0 голосов
/ 24 февраля 2019

Я понимаю, что вам нужны две вещи:

  • способ записи произвольных символов Unicode в файл и
  • Совместимость с Python 2/3.

Использование open('out1.txt','w') нарушает оба:

  • Выходной текстовый поток открывается с кодировкой по умолчанию, которая на вашей платформе (по-видимому, Windows) называется CP-1252.Этот кодек поддерживает только подмножество Unicode, например.не хватает всех смайликов.
  • Функция open значительно отличается в разных версиях Python.В Python 3 это функция io.open, которая предлагает большую гибкость, такую ​​как указание кодировки текста.В Python 2 возвращаемый дескриптор файла обрабатывает 8-разрядные строки, а не строки Unicode (текст).
  • Существует также проблема переносимости, о которой вы можете не знать: кодировка по умолчанию для ввода-вывода зависит от платформы, т.е.,люди, работающие с вашим кодом, могут увидеть разные значения по умолчанию в зависимости от ОС и локализации.

Вы можете избежать всего этого с помощью io.open('out1.txt', 'w', encoding='utf8'):

  • Используйте кодировку, которая поддерживает все символынеобходимо.Использование обнаруженной входной кодировки должно работать, если обработка не вводит символы вне поддерживаемого диапазона.Использование одного из кодеков UTF всегда будет работать, причем UTF-8 наиболее широко используется для текстовых файлов.Обратите внимание, что некоторые приложения Windows (например, «Блокнот»), как правило, не понимают UTF-8.
  • Модуль io был перенесен в Python 2.7.Как правило, это считается совместимостью с Py2 / 3, поскольку поддержка версий <= 2.6 прекратилась довольно давно. </li>
  • Уточните кодировку, используемую при открытии текстовых файлов.Могут быть сценарии, в которых кодирование по умолчанию, зависящее от платформы, имеет смысл, но обычно требуется контроль.

Примечание: вы упоминаете простую эвристику для обнаружения входного кодека.Если на самом деле нет способа получить эту информацию, вам следует рассмотреть возможность использования chardet .

0 голосов
/ 23 февраля 2019

Будьте явным.Вы открыли для записи, используя кодировку по умолчанию.Что бы это ни было, оно не поддерживает все кодовые точки Unicode.Откройте файл с кодировкой UTF-8, которая поддерживает поддерживает все кодовые точки Unicode:

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