Шаг 94.
Python: сборник рецептов.
Файлы и ввод-вывод. Добавление или изменение кодировки уже открытого файла

    На этом шаге мы рассмотрим решение этой задачи.

Задача

    Вы хотите добавить или изменить кодировку Unicode уже открытого файла, не закрывая его.

Решение

    Если вы хотите добавить кодирование/декодирование в Unicode уже существующему файловому объекту, открытому в бинарном режиме, оберните его объектом io.TextIOWrapper(). Например:

import urllib.request 
import io

u = urllib.request.urlopen('http://www.python.org') 
f = io.TextIOWrapper(u, encoding='utf-8') 
text = f.read()

    Если вы хотите изменить кодировку файла, открытого в текстовом режиме, используйте метод detach() для удаления существующего слоя текстовой кодировки перед заменой его новым. Вот пример изменения кодировки в sys.stdout:

>>> import sys
>>> sys.stdout.encoding
'utf-8'
>>> sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='1atin-1')
>>> sys.stdout.encoding 
'latin-1'
>>>

    Если вы это сделаете, то можете "сломать" вывод вашего терминала. Приведенный выше пример - это просто иллюстрация подхода.

Обсуждение

    Система ввода-вывода построена на последовательности слоев. Вы можете увидеть эти слои, если попробуете сделать следующее:

>>> f = open('sample.txt','w')
>>> f
<_io.TextIOWrapper name='sample.txt' mode='w' encoding='utf-8'>
>>> f.buffer
<_io.BufferedWriter name='sample.txt'>
>>> f.buffer.raw
<_io.FileIO name='sample.txt' mode='wb' closefd=True>
>>> 

    В этом примере io.TextIOWrapper - это слой для обработки текста, который кодирует и декодирует в Unicode, ioBufferedWriter - буферизированный слой ввода-вывода, который работает с бинарными данными, а ioFileIO - "сырой файл", представляющий низкоуровневый файловый дескриптор в операционной системе. Добавление или изменение текстовой кодировки вовлекает добавление и изменение только верхнего слоя - io.TextIOWrapper.

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

>>> f
<_io.TextIOWrapper name='sample.txt' mode='w' encoding='utf-8'>
>>> f = io.TextIOWrapper(f.buffer, encoding='latin-1')
>>> f
<_io.TextIOWrapper name='sample.txt' encoding='latin-1'>
>>> f.write('Hello')
Traceback (most recent call last):
  .   .   .   .
ValueError: I/O operation on closed file.
>>> 

    Это не работает, поскольку изначальное значение f было уничтожено, а лежащий в основе файл закрыт.

    Метод detach() отделяет верхний слой файла и возвращает следующий, более низкоуровневый слой. Далее высший слой уже нельзя использовать. Например:

>>> f = open('sample.txt', 'w')
>>> f
<_io.TextIOWrapper name='sample.txt' mode='w' encoding='utf-8'>
>>> b = f.detach()
>>> b
<_io.BufferedWriter name='sample.txt'>
>>> f.write('Hello')
Traceback (most recent call last):
  .   .   .   .
ValueError: underlying buffer has been detached
>>> 

    Однако после отделения вы можете добавить новый верхний слой возвращаемому результату. Например:

>>> f = io.TextIOWrapper(b, encoding='latin-1')
>>> f
<_io.TextIOWrapper name='sample.txt' encoding='latin-1'>
>>> 

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

>>> sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='ascii', \
         errors='xmlcharrefreplace')
>>> print('Jalape\u00f1o')
Jalape&#241;o
>>>

    Заметьте, как не входящий в ASCII символ ñ был заменен на &#241; в выводе.

    На следующем шаге мы рассмотрим запись байтов в текстовый файл.




Предыдущий шаг Содержание Следующий шаг