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

В этой статье вы научитесь считывать данные не только с клавиатуры, но и из файлов, а также записывать результаты работы программ обратно в текстовые и JSON-файлы. Разберётесь, как устроен стандартный поток ввода, научитесь использовать функции open(), read(), write() и менеджер контекста with. Кроме того, освоите работу с форматом JSON — научитесь читать и сохранять структурированные данные с помощью модуля json. Эти знания помогут вам строить программы, которые взаимодействуют с внешними источниками данных и сохраняют результаты в удобном виде.

Ключевые вопросы статьи

  • Что такое стандартный поток ввода и как с ним работать в Python?
  • Какие методы позволяют считывать и записывать данные из текстовых файлов?
  • Почему важно использовать менеджер контекста with при работе с файлами?
  • Как устроен формат JSON и чем он отличается от обычного текста?
  • Как использовать модуль json для чтения и записи структурированных данных?

Что такое стандартный поток ввода и как с ним работать в Python

Ранее вы уже использовали функцию input() для чтения данных с клавиатуры. Этот способ хорош, если вы точно знаете, сколько строк нужно ввести или как выглядит строка для завершения ввода.

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

Подключение stdin

Чтобы использовать stdin, его нужно импортировать:

from sys import stdin

Поскольку stdin — это итератор, по его строкам можно пройти в цикле:

from sys import stdin

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

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

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

Чтобы завершить ввод:

  • в Windows — нажмите Ctrl + Z, затем Enter;
  • в Linux и macOS — нажмите Ctrl + D.

Результат работы программы:

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

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

from sys import stdin

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

# Теперь результат:
# ['Первая', 'Вторая', 'Третья']

Чтение всех строк сразу

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

from sys import stdin

lines = stdin.readlines()
print(lines)

Чтение всего ввода в одну строку

from sys import stdin

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

# Пример вывода:
# ['Первая\nВторая\nТретья\n']

Здесь результат обёрнут в список, чтобы явно показать символы перехода на новую строку. Переменная text содержит весь ввод целиком — со всеми символами \n.

Какие методы позволяют считывать и записывать данные из текстовых файлов

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

Что такое файл

Файл — это именованная область данных на диске. Данные в нём хранятся как последовательность байтов. Если файл текстовый, его можно интерпретировать как последовательность символов. Для корректной работы с символами нужно знать кодировку (чаще всего используется UTF-8).

Функция open()

open(file, mode='r', encoding=None)

Аргумент mode определяет режим доступа к файлу:

  • 'r' — чтение (по умолчанию). Файл должен существовать, иначе будет ошибка.
  • 'w' — запись с очисткой содержимого. Если файл существует, его содержимое удаляется. Если не существует — создаётся.
  • 'a' — добавление в конец файла. Новый текст будет дописан после уже имеющегося. Если файл не существует — создаётся.
  • 'r+' — чтение и запись, без удаления содержимого. Файл должен существовать. Запись идёт поверх существующего текста, начиная с начала файла.
  • 'w+' — чтение и запись, с удалением содержимого. Файл очищается или создаётся заново. Подходит, если вы хотите сначала записать, а потом прочитать.
  • 'a+' — чтение и добавление. Файл открывается в режиме добавления, но можно также его читать. При записи — курсор всегда в конце.

Важно

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

Пример: открытие файла для чтения

Допустим, у нас есть файл input_1.txt, содержащий следующий текст:

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

Прочитаем его содержимое:

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

Построчное чтение файла

По открытому файлу можно пройти в цикле:

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

Почему появляются пустые строки

Мы ожидали, что строки выведутся одна под другой. Но фактически на экране будет:

Привет!

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

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

Причина: каждая строка из файла уже заканчивается символом переноса строки \n, а функция print() по умолчанию тоже добавляет \n. В результате между строками получается двойной отступ.

Как это исправить

Можно удалить символ переноса в конце строки с помощью .rstrip("\n"):

print(line.rstrip("\n"))

Теперь вывод будет выглядеть так:

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

Всё ровно — без лишних пустых строк.

Почему важно использовать менеджер контекста with при работе с файлами

Обычно после работы с файлом его нужно закрывать вручную:

file_in = open("input_1.txt", encoding="UTF-8")
# ... работа с файлом ...
file_in.close()

Если этого не сделать, файл может остаться открытым:

  • он будет заблокирован для других программ;
  • данные могут не сохраниться сразу;
  • при ошибке в коде close() может не выполниться вовсе.

Безопасный способ — использовать with

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

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

Однако, использование метода 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)

Что делает программа:

  1. Открывает (или создаёт) файл output_1.txt в режиме записи. Если файл уже существует, его содержимое будет удалено.
  2. Записывает в него одну строку с символами перевода строки \n внутри — это создаст три строки в файле.
  3. Метод write() возвращает количество записанных символов, включая все пробелы и \n.
  4. Эта длина (в нашем случае — 55) выводится в консоль.

Запись списка строк

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

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

# Содержимое выходного файла:
# Это первая строка
# А вот и вторая
# И третья - последняя

Использование print() для записи в файл

Функция 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.
Он позволяет преобразовать данные из JSON-файла в вашей программе в стандартные типы данных Python, а также способен выполнить обратную операцию — для записи данных обратно в файл.

Чтение JSON-файла

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

import json
from pprint import pprint

with open("data.json", encoding="UTF-8") as file_in:
    records = json.load(file_in)
pprint(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-файл был преобразован в список словарей, а каждый словарь — это запись с информацией о студенте. Для обработки таких объектов можно применять стандартные методы и операции Python.

Запись JSON-файла

После того как вы изменили структуру данных в программе (например, обновили одно из полей словаря), результат можно сохранить обратно в JSON-файл.

Для этого используется метод dump() из модуля json. Он преобразует объект Python в строку в формате JSON и записывает её в файл.

Рассмотрим важные аргументы функции json.dump():

  • ensure_ascii — если True (по умолчанию), все не-ASCII-символы сохраняются как \uXXXX. При False сохраняются как есть (нужно для букв на кириллице).
  • indent — задаёт отступы для форматирования:
    — None (по умолчанию) — весь JSON в одну строку;
    — число — количество пробелов на уровень вложенности.
  • sort_keys — если True, ключи в словаре будут отсортированы.
Пример

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

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 автоматически преобразует ключи словаря в строки, если они были, например, целыми числами.
Рассмотрим пример:

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"
}

Обратите внимание: ключи 1, 2, 3 были целыми числами в Python, но в JSON все ключи обязательно должны быть строками — они были автоматически преобразованы при записи.

Что дальше

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

Далее — финальная статья третьей главы, в котором мы кратко подведём итоги.

Ключевые выводы статьи

  • Стандартный поток ввода stdin позволяет считывать произвольное количество строк из консоли, что удобно при обработке входных данных без заранее известного объёма.
  • С помощью методов read(), readlines() и write() можно считывать и записывать данные из текстовых файлов, построчно или целиком.
  • Менеджер контекста with гарантирует, что файл будет закрыт автоматически, даже если в программе произойдёт ошибка.
  • Формат JSON используется для хранения структурированных данных и поддерживается большинством языков программирования, включая Python.
  • Модуль json позволяет легко преобразовывать JSON-файлы в объекты Python (load) и обратно (dump), а также управлять форматированием и кодировкой при записи.