3.5. Потоковый ввод/вывод. Работа с текстовыми файлами. JSON

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

Стандартный поток ввода stdin

До этого момента для ввода данных с клавиатуры мы использовали функцию input(). И этот способ хорошо работает, когда мы знаем, сколько строк с данными предстоит ввести, либо знаем, какая строка будет сигналом для остановки ввода данных. Но что делать, если количество входных строк мы не знаем, а строки для остановки ввода не предусмотрено? В таком случае следует использовать так называемый стандартный поток ввода. В него попадают все строки, которые вводит пользователь с клавиатуры. Программа может забирать строки из потока ввода одну за другой, а сами строки при этом удаляются из потока.

Чтобы подключить стандартный поток ввода stdin в программу, необходимо его импортировать из модуля sys:

from sys import stdin

Так как поток ввода stdin по сути выступает итератором, по его элементам (введённым строкам) можно пройти в цикле. Использование потока ввода показано в следующей программе:

from sys import stdin

lines = []
for line in stdin:
    lines.append(line)
print(lines)

Запустим программу и введём следующие данные:

Первая
Вторая
Третья

Теперь вводить данные мы можем практически бесконечно. Допустим, что после ввода третьей строчки нам нужно показать интерпретатору, что ввод данных завершён, можно выйти из цикла и продолжить работу программы. Для этого необходимо послать в поток ввода специальный символ — EOF (end of file), нажав в консоли сочетание клавиш Ctrl+Z в ОС Windows либо Ctrl+D в ОС Linux и macOS. Когда в консоли Windows появится знак ^Z, нужно нажать клавишу Enter — ввод прекратится, а программа выдаст список введённых пользователем строк:

['Первая\n', 'Вторая\n', 'Третья\n']

Обратите внимание — каждая строка заканчивается символом перехода на новую строку. Если этот символ мешает дальнейшей обработке строк, его можно убрать, применив к каждой строке метод rstrip("\n") перед добавлением в список:

from sys import stdin

lines = []
for line in stdin:
    lines.append(line.rstrip("\n"))
print(lines)

Тогда результат работы программы для тех же введённых строк будет:

['Первая', 'Вторая', 'Третья']

Сохранить строки из потока ввода в список можно ещё проще — достаточно использовать метод readlines(). Главное — не забудьте о том, что символ "\n" будет в конце каждой введённой строки:

from sys import stdin

lines = stdin.readlines()
print(lines)

Если необходимо сохранить все данные из потока ввода в одну строковую переменную, это можно сделать с помощью метода read():

from sys import stdin

text = stdin.read()
print([text])

Вывод программы для прежних введённых строк:

['Первая\nВторая\nТретья\n']

Мы поместили переменную text в список для того, чтобы показать наличие в ней символов новой строки, а не просто сделать эти переходы при выводе. Как видим, переменная text в примере будет содержать все введённые в поток ввода символы, включая "\n".

Файловый ввод-вывод

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

Файл — это именованная область данных на носителе информации. Данные в файле представляются как последовательность байтов. Текстовые файлы можно прочитать как последовательность символов, если мы знаем их кодировку. В данном параграфе мы рассмотрим работу с текстовыми файлами в кодировке UTF-8.

Для работы с файлами в Python есть встроенная функция open(), которая имеет следующие основные аргументы:

  • file — строка, содержащая путь до файла. Путь может быть абсолютным (от буквы диска в Windows или от корневой папки в Linux) или относительным (от папки, в которой находится запущенная программа);
  • mode — режим доступа к файлу. Рассмотрим следующие режимы:
    • "r" — режим чтения текстового файла; является режимом по умолчанию;
    • "w" — режим записи нового текстового файла; файл всегда создаётся заново;
    • "a" — режим записи текстового файла; если файла нет, то он создаётся, иначе данные добавляются в конец существующего файла;
    • "+" — добавляется в конец обозначения предыдущих режимов и позволяет работать с файлом одновременно в режиме чтения и записи.
  • encoding — строка, содержащая обозначение кодировки текстового файла (например, "UTF-8").

Прочитаем текстовый файл в кодировке UTF-8. Вы можете создать файл самостоятельно в «Блокноте», но не забудьте при сохранении указать кодировку UTF-8:

file_in = open("input_1.txt", encoding="UTF-8")

Обратите внимание на то, что кодировка UTF-8 является стандартной для ОС Linux и macOS. А вот для Windows её нужно обязательно указывать в именованном аргументе encoding, так как кодировкой по умолчанию для ОС Windows является cp-1251. Также следует учитывать, что для поддержки кросс-платформенности программ кодировку всё равно лучше указывать в явном виде.

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

file_in = open("input_1.txt", encoding="UTF-8")
for line in file_in:
    print(line)
file_in.close()

Вывод программы для файла из примера:

Привет!

Это пример текстового файла.

А это — последняя строка, которая заканчивается переходом на новую.

Мы видим, что между выводом строк из файла добавились пустые строчки. Это произошло потому, что в конце каждой считанной строки добавляется символ \n, а функция print() также добавляет \n. Эта ситуация похожа на работу с stdin, и убрать \n можно так же — вызовом метода rstrip("\n").

Обратите внимание: после работы с файлом его необходимо закрыть с помощью метода close(). Если этого не сделать, файл будет недоступен другим программам, так что не забывайте его закрывать, если он больше не используется. А чтобы не забыть, можно воспользоваться менеджером контекста with, который закроет файл даже в случае возникновения ошибок в процессе работы. Блок менеджера контекста имеет отступ в четыре пробела:

with open("input_1.txt", encoding="UTF-8") as file_in:
    for line in file_in:
        print(line.rstrip("\n"))

Вывод программы:

Привет!
Это пример текстового файла.
А это — последняя строка, которая должна закончиться переходом на новую.

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

Для получения полного списка строк текстового файла используется метод readlines():

with open("input_1.txt", encoding="UTF-8") as file_in:
    lines = file_in.readlines()
print(lines)

Вывод программы:

['Привет!\n', 'Это пример текстового файла.\n', 'А это — последняя строка, которая должна закончиться переходом на новую.\n']

Для чтения символов файла в строковую переменную используется метод read(). Прочитаем 10 символов файла:

with open("input_1.txt", encoding="UTF-8") as file_in:
    symbols = file_in.read(10)
print([symbols])

Вывод программы:

['Привет!\nЭт']

Если в методе read() не указать количество считываемых символов, то прочитается весь файл.

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

with open("output_1.txt", "w", encoding="UTF-8") as file_out:
    n = file_out.write("Это первая строка\nА вот и вторая\nИ третья — последняя\n")
print(n)

Метод write() возвращает количество записанных в файл символов.

Для записи строк из списка в файл используется метод writelines(). Этот метод записывает строки в файл по очереди, без разделителя. Пример его использования:

lines = ["Это первая строка\n", "А вот и вторая\n", "И третья — последняя\n"]
with open("output_2.txt", "w", encoding="UTF-8") as file_out:
    file_out.writelines(lines)

Содержимое выходного файла:

Это первая строка
А вот и вторая
И третья — последняя

Функция print() может быть использована для вывода данных в файл. Для этого нужно передать ей в аргумент file файловый объект:

with open("output_3.txt", "w", encoding="UTF-8") as file_out:
    print("Вывод в файл с помощью функции print()", file=file_out)

Содержимое выходного файла:

Вывод в файл с помощью функции print()

JSON

Существуют такие форматы текстовых файлов, которые могут хранить в себе информацию, структурированную по некоторым правилам. Один из таких форматов — JSON (JavaScript Object Notation). Как видно из названия, он был создан для языка JavaScript, но в настоящее время является независимым от него и может использоваться практически с любым языком программирования.

Для значений в JSON можно использовать:

  • строки — для обозначения строки используются строго двойные кавычки;
  • числа — целые и вещественные;
  • логические значения true и false — записываются, в отличие от Python, со строчной буквы.

Для хранения неупорядоченных наборов данных в JSON можно использовать записи, то есть пары «ключ: значение» (аналог словаря в Python). Также можно хранить упорядоченные последовательности значений (аналог списка в Python).

Посмотрим, что представляет собой JSON-файл на примере. Предположим, что нужно хранить структурированную информацию о студентах следующего вида: фамилия (last_name), имя (first_name), отчество (patronymic), дата рождения (date_of_birth), номер группы (group_number), список номеров телефонов (phones_numbers).
Тогда список с информацией о студентах можно представить в следующем виде:

[
  {
    "last_name": "Иванов",
    "first_name": "Иван",
    "patronymic": "Иванович",
    "date_of_birth": "01.01.2001",
    "group_number": 1,
    "phone_numbers": [
      "+7 111 111 1111",
      "+7 111 111 1112"
    ]
  },
  {
    "last_name": "Петров",
    "first_name": "Пётр",
    "patronymic": "Петрович",
    "date_of_birth": "10.10.2001",
    "group_number": 1,
    "phone_numbers": [
      "+7 111 111 1113",
      "+7 111 111 1114"
    ]
  }
]

Мы видим, что в JSON-файле находится список из двух записей, очень похожих на словарь Python.

Для более удобной работы с JSON-файлами существует стандартный модуль json. Он позволяет преобразовать данные из JSON-файла в вашей программе в стандартные типы данных Python, а также способен выполнить обратную операцию для записи данных в JSON-файл.

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

Прочитаем JSON-файл с данными из примера:

import json

with open("data.json", encoding="UTF-8") as file_in:
    records = json.load(file_in)
print(records)

Вывод программы:

[{'date_of_birth': '01.01.2001',
'first_name': 'Иван',
'group_number': 1,
'last_name': 'Иванов',
'patronymic': 'Иванович',
'phone_numbers': ['+7 111 111 1111', '+7 111 111 1112']},
{'date_of_birth': '10.10.2001',
'first_name': 'Пётр',
'group_number': 1,
'last_name': 'Петров',
'patronymic': 'Петрович',
'phone_numbers': ['+7 111 111 1113', '+7 111 111 1114']}]

Из примера видно, что JSON-файл был преобразован в список словарей, а каждый словарь — это запись с информацией о студенте. Для обработки стандартных объектов мы можем применить известные операции, функции и методы. Для записи изменённых данных в JSON-файл используется метод dump(). Рассмотрим некоторые важные его аргументы:

  • ensure_ascii. Имеет значение по умолчанию True, при котором все не-ASCII-символы при выводе в файл представляют собой юникод-последовательности вида \uXXXX (коды символов в таблице кодировки). Если аргумент имеет значение False, такие символы будут записаны в виде символов, а не их кодов. В примере используются русские символы, поэтому необходимо передать в аргумент значение False.
  • indent. Задаёт вид отступа для удобства чтения данных человеком. По умолчанию аргумент имеет значение None, а данные записываются в файл одной строкой. Если задать строку вместо None, то эта строка будет использоваться в качестве отступа. Если задать число больше 0, то отступ будет состоять из такого же количества пробелов.
  • sort_keys. Позволяет отсортировать ключи словаря с данными. По умолчанию имеет значение False. Для сортировки ключей необходимо передать в аргумент значение True.

Изменим номер группы у второго студента и сохраним данные в исходный файл:

import json

with open("data.json", encoding="UTF-8") as file_in:
    records = json.load(file_in)
records[1]["group_number"] = 2
with open("data.json", "w", encoding="UTF-8") as file_out:
    json.dump(records, file_out, ensure_ascii=False, indent=2)

Содержимое файла data.json после работы программы:

[
  {
    "last_name": "Иванов",
    "first_name": "Иван",
    "patronymic": "Иванович",
    "date_of_birth": "01.01.2001",
    "group_number": 1,
    "phone_numbers": [
      "+7 111 111 1111",
      "+7 111 111 1112"
    ]
  },
  {
    "last_name": "Петров",
    "first_name": "Пётр",
    "patronymic": "Петрович",
    "date_of_birth": "10.10.2001",
    "group_number": 2,
    "phone_numbers": [
      "+7 111 111 1113",
      "+7 111 111 1114"
    ]
  }
]

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

import json

records = {1: "First",
           2: "Second",
           3: "Third"}
with open("output.json", "w", encoding="UTF-8") as file_out:
    json.dump(records, file_out, ensure_ascii=False, indent=2)

Содержимое файла output.json после работы программы:

{
  "1": "First",
  "2": "Second",
  "3": "Third"
}

Отмечайте параграфы как прочитанные чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

Здесь можно найти единомышленников, экспертов и просто интересных собеседников. А ещё — получить помощь или поделиться знаниями.
Вступить
Сообщить об ошибке
Предыдущий параграф3.4. Встроенные возможности по работе с коллекциями

Здесь мы рассмотрим функции стандартной библиотеки itertools для обработки коллекций.

Следующий параграф4.1. Функции. Области видимости. Передача параметров в функции

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