Тонкости чтения из текстовых файлов в Python Python

Тонкости чтения из текстовых файлов в Python

Мы обычно никогда не заморачиваемся с нюансами применения той или иной функции или популярной конструкции Python, бездумно используя первый же попавшийся, взятый в бездонных недрах Internet, работоспособный вариант этих инструментариев. Однако, все это приводит к тому, что мы искусственно игнорируем те потенциальные возможности вышеназванных инструментариев, которые очень помогли бы нам, как в оптимизации кода, разрабатываемых нами программ, так и в улучшении их технических характеристик.

Все высказанное в полной мере касается и программной реализации процедуры чтения текстовых файлов в Python, тонкостям которой и посвящена данная статья.

Два способа открытие и чтение текстовых файлов

Для каждой своей программы интерпретатор Python в момент ее запуска открывает три следующие потока:

  • stdin - стандартный поток ввода;
  • stdout - стандартный поток вывода;
  • stderr - стандартный поток сообщений об ошибках.

Два первые потока по сути являются основой для работы с файлами. Так, благодаря потоку stdin можно читать из файлов, а благодаря потоку stdout - записывать в них. Данные операции можно осуществлять как из командной строки при запуске соответствующей Python программы, так и в коде самой такой программы при помощи стандартной Python функции open()*.

Давайте посмотрим, как может быть практически реализован процесс чтения из файлов двумя вышеуказанными способами.

Примечание: С целью закрепления материала посредством выполнения приведенных в этой статье практических примеров, рекомендуется сначала скачать соответствующие текстовые (Markdown) файлы mysli-velikikh.md и english-sayings.md, а затем сохранить их в той директории, где вы собираетесь далее работать.

Чтение текстовых файлов при помощи потока “stdin”

Процесс чтения из файла с помощью потока stdin попробуем рассмотреть на примере командной строки. С этой целью напишем и сохраним в файл test-read.py следующую программку:

import sys
text = sys.stdin.read()
print(text)

Затем, вызовем эту программку для вывода на консоль содержимого файла english-sayings.md. Это мы можем сделать либо с помощью оболочки PowerShell, по умолчанию подключаемой к такой популярной для Python среде разработки как PyCharm, либо посредством использования обычного терминала (интерпретатора командных строк) в нашей операционной системы.

Для оболочки PowerShell командная строка, вызывающая вышеназванную программку, основывается на применении командлета (специальной команды) Get-Content и имеет следующий вид:

Get-Content english-sayings.md | python test-read.py

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

python test-read.py  <  english-sayings.md
Результатом выполнения любой из этих команд будет выведенное на консоль содержимое файла english-sayings.md с популярными английскими цитатами:

1. Wise men speak because they have something to say; fools because they have to
   say something. 
2. Chop your own wood and it will warm you twice
3. I don't care what you think about me. I don't think about you at all.
4. Work hard to get what you like, otherwise you'll be forced to just like what
   you get.
5. In the End, we will remember not the words of our enemies, but the silence of
   our friends.

Примечание: Если мы попытаемся открыть через всю ту же командную строку текстовые файлы, например, с кириллическим текстом, то скорее всего вместо данного текста увидим сплошные знаки вопросов. Это объясняется тем, что стандартный поток ввода из файлов через командную строку работает только с однобайтной кодировкой. Следовательно, те символы, коды которых превышают 128 будут отображаться в ней в виде знаков вопросов.

Чтобы убедиться в том, что командная строка с использованием stdin не пригодна для вывода текста на кириллицы, попробуйте сами запустить нашу программку test-read.py для вывода на консоль файла mysli-velikikh.md.

Как видим из вышеприведенных примеров, применение командной строки для ввода данных в программы из файлов сопряжено с рядом ограничений и неудобств особенно при обработке этими программами множеств текстовых файлов с расширенным набором символов и необходимостью запуска их в разных операционных средах. Именно поэтому такой способ чтения файлов на основе непосредственного использования потока stdin в настоящее время практически не используется.

Однако, принцип использования как опробованного нами стандартного потока ввода stdin, так и стандартного потока вывода stdout лежит в основе “подкапотной” работы стандартной функции open(), которая в свою очередь является базовым инструментарием по обработке файлов при программировании на Python.

Открытие и чтение файлов посредством стандартной Python функции open()

Второй способ чтения текстовых файлов, использующий стандартную Python функцию open() попробуем рассмотреть на примере файла mysli-velikikh.md:

file_object = open('mysli-velikikh.md')
print(file_object)
<_io.TextIOWrapper name='mysli-velikikh.md' mode='r' encoding='cp1251'>

Из приведенного выше примера видно, что функция open() возвращает нам _io.TextIOWrapper - объект буферизированного текстового потока, который в сущности можно воспринимать, как обычный файловый объект, обеспечивающий доступ к файлам (для нашего примера, к файлу mysli-velikikh.md) при помощи весьма внушительного перечня методов и свойств, основными из которых являются методы .read() и .write().

Примечание: Обратите внимание, что одной из характеристик файлового объекта из предыдущего примера является encoding='cp1251', обозначающая что наш файл mysli-velikikh.md при дальнейшей обработки будет восприниматься в кодировке cp1251, являющейся кодировкой по умолчанию для операционных систем Windows. В нашем случае для файла mysli-velikikh.md данная кодировка является не правильной, поскольку он изначально был сохранен в кодировке UTF8. Какие от этого могут быть последствия описано далее в этой статье.

Попробуем прибегнув к методу .read() в нижеследующем коде вывести на консоль содержимое нашего файлаmysli-velikikh.md:

file_object = open('mysli-velikikh.md', encoding='utf8')
text = file_object.read()
print(text)

# 1. Что разум человека может постигнуть и во что он может поверить,
#    того он способен достичь
#                              *Наполеон Хилл, журналист и писатель*

# 2. Стремитесь не к успеху, а к ценностям, которые он дает
#                                        *Альберт Эйнштейн*

# ...

Обратите внимание, что в этом примере кода при вызове функции open() у нас добавился еще один поименованный аргумент encoding приравниваемый к кодировке UTF8, т.е. к той кодировке, в которой у нас изначально был сохранен файл mysli-velikikh.md. Дело в том, что как мы видели из параметров файлового объекта file_object, он первоначально был сформирован в кодировке cp1251. Но, так как эта кодировка не совпадает с кодировкой обрабатываемого нами файла, то без указания нужной нам кодировки в поименованном аргументе encoding наша программа выдала бы ошибку о невозможности декодирования из-за несоответствия кодов символов:

UnicodeDecodeError: 'charmap' codec can't decode byte 0x98 in position 935:
                     character maps to <undefined>

Закрытие файлов после работы с ними в программах Python

По окончанию каких-либо работ с файлами, мы обязательно должны эти файлы закрыть. В противном случае, оставленные открытыми файлы могут привести к ошибкам ОЗУ вплоть до краха операционной системы. В частности, для закрытия файлов, можно воспользоваться метода .close(). Но, после применения данного метода, мы с нашим файловым объектом уже ничего не сможем сделать:

file_object = open('mysli-velikikh.md', encoding='utf8')
file_object.close()
text = file_object.read()
print(text)

# Traceback (most recent call last):
#   File "<stdin>", line 3, in <module>
# ValueError: I/O operation on closed file.
В этом примере видно, что попытка чтения из файла после его закрытия приводит к ошибке, гласящей о том, производится операция ввода-вывода для уже закрытого файла.

Примечание: Важно отметить, что после завершения любых программ на Python, интерпретатор сам автоматически закрывает любые файлы, которые в этих программах использовались. Это достаточно сильно снижает риск возникновения непредвиденных ситуаций из-за не своевременно закрытых файлов, но все-равно не обеспечивает стопроцентного предотвращения таких ситуаций вследствие ошибок, которые могут произойти с файлами в процессе выполнения использующих их программ.

Закрытие файлов вручную методом .close()

Раннее мы уже говорили о важности своевременного закрытия файлов и о том, что их можно закрывать используя такой метод файлового объекта, как .close(). Но, что если у нас при обработке файла в промежутке между его открытием функцией open() и закрытием методом .close() произойдет какое-либо исключение?

Попробуем смоделировать данную ситуацию:

1
2
3
4
5
6
7
8
file_object = open('mysli-velikikh.md', encoding='utf8')
       poryadkovyy_nomer = int(file_object.read().splitlines()[0].split()[0])
file_object.close()
print(poryadkovyy_nomer)

# Traceback (most recent call last):
#   File "<stdin>", line 2, in <module>
# ValueError: invalid literal for int() with base 10: '1.'

Из данного примера видно, что до закрытие файла mysli-velikikh.md, при его чтении через файловый объект file_object в строке 2 у нас возникает ошибка относительно некорректного (не числового) аргумента функции int() равного '1.'. Вместе с тем, наш файловый объект file_object после возникновения вышеназванной ошибки все еще остается открытым, что чревато возникновением целого ряда ошибок

print(file_object.closed)
False

Использование with блока для автоматического закрытия файлов

Описанная в предыдущем разделе статьи проблема решается путем применения with блока, который по завершению своего выполнения обеспечивает автоматическое закрытие файла, предварительно открытого в теле данного блока функцией open():

with open('mysli-velikikh.md', encoding='utf8') as file_object:
    poryadkovyy_nomer = int(file_object.read().splitlines()[0].split()[0])
print(poryadkovyy_nomer) 

Блок with работает с контекстным менеджером, являющемся своеобразным инструментарием, выполняющим за нас рутинную работу по управлению вычислительными ресурсами. В частности, контекстные менеджеры, к коем также относятся и файлы, позволяют выделять и освобождать ресурсы строго в соответствии с их потребностями.

Таким образом, при входе в with блок, Python интерпретатор сначала информирует соответствующий объект контекстного менеджера о необходимости выполнения некоего специального входного кода. При выходе же из этого with блока, который определяется уменьшением уровня отступа в коде, интерпретатор Python повторно информирует вышеназванный объект контекстного менеджера о необходимости выполнения им иного специального кода, предназначенного уже для выхода из этого блока. Следовательно, вполне очевидно, что для файловых объектов при их выходе из with блоков будет всегда выполняться метод .close() для автоматического закрытия ассоциируемых с этими объектами файлов. Ввиду этого, использование with блоков для обработки файлов в наших Python программах всегда может восприниматься нами, как своеобразный предохранитель от возникновения разного рода исключения, связанных с несвоевременным закрытием, обрабатываемых в этих программах, файлов. объекту диспетчера контекста, чтобы он мог выполнить некоторый специальный код выхода.

Давайте последнее утверждение проверим на практике:

1
2
3
4
5
6
7
with open('mysli-velikikh.md', encoding='utf8') as file_object:
    poryadkovyy_nomer = int(file_object.read().splitlines()[0].split()[0])
print(poryadkovyy_nomer) 

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: '1.'

Несмотря на то, что выполнение данного примера показывает нам все ту же ошибку, что была и в примере с методом .close(), однако наш файл mysli-velikikh.md благодаря использованию блока with теперь уже будет закрыт.

print(file_object.closed)
True

Резюме

Для открытия и чтения текстовых файлов безусловно нужно выбирать стандартную Python функцию open(), которая в отличии от непосредственного использования стандартного потока ввода stdin, намного проще в использовании, а также имеет гораздо большую гибкость, универсальность и номенклатуру возможностей.

Вышеназванная функция open() для каждого открываемого файла возвращает нам соответствующий файловый объект, имеющий весьма внушительный перечень методов и свойств для доступа и обработки, ассоциируемого с ним файла. В частности, для чтения из текстовых файлов применяется такой метод файлового объекта, как .read(), который возвращает все содержимое файла из соответствующего файлового объекта.

При использовании файлов в кодах наших Python программ мы обязательно должны следить за своевременным закрытием данных файлов, всегда осуществляя их закрытие после завершения работы с ними. При этом следует помнить, что Python интерпретатор всегда автоматически закрывает все используемые программами файлы при их завершении. Однако полагаться на это следует лишь тогда, когда мы разрабатываем очень простенькие программки, состоящие из нескольких строк. В иных же случаях всегда рекомендуется использовать with блоки, обеспечивающие автоматическое закрытие используемых нами файлов сразу же после того, как мы закончили с ними работать.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

🏆 Hello, world! Python
Новичок
🏆 Hello, world!

Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.

2022-10-04
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?

2022-10-19
Условные конструкции и сопоставление структурных шаблонов Шпаргалки
Новичок
Условные конструкции и сопоставление структурных шаблонов

Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов

2022-11-09