Уроки курса
1 Введение в Python и философия дзен
30 мин
2 Переменные и динамическая типизация
30 мин
3 Базовые типы данных: числа, строки и булевы значения
30 мин
4 Изменяемые и неизменяемые объекты (Mutable vs Immutable)
30 мин
5 Форматирование строк и f-строки
30 мин
6 Углубленная работа со списками
30 мин
7 Кортежи и их особенности
30 мин
8 Словари под капотом
30 мин
9 Множества и математические операции
30 мин
10 Генераторы списков (List Comprehensions)
30 мин
11 Генераторы словарей и множеств
30 мин
12 Встроенные функции для коллекций
30 мин
13 Условные операторы и логические выражения
30 мин
14 Циклы while и управление потоком
30 мин
15 Итерация с циклом for
30 мин
16 Конструкции for...else и while...else
30 мин
17 Функции enumerate и zip
30 мин
18 Создание собственных функций (def)
30 мин
19 Позиционные и именованные аргументы
30 мин
20 Проблема изменяемых аргументов по умолчанию
30 мин
21 Произвольное число аргументов (*args и **kwargs)
30 мин
22 Область видимости переменных (LEGB)
30 мин
23 Анонимные функции (lambda)
30 мин
24 Функции высшего порядка
30 мин
25 Замыкания (Closures)
30 мин
26 Введение в объектно-ориентированное программирование
30 мин
27 Атрибуты классов и экземпляров
30 мин
28 Магический метод __init__
30 мин
29 Методы экземпляра
30 мин
30 Инкапсуляция и сокрытие данных
30 мин
31 Декоратор @property
30 мин
32 Наследование классов
30 мин
33 Переопределение методов и функция super()
30 мин
34 Полиморфизм в Python
30 мин
35 Магические методы строк (__str__ и __repr__)
30 мин
36 Обработка исключений (try-except)
30 мин
37 Блоки else и finally
30 мин
38 Генерация собственных исключений (raise)
30 мин
39 Открытие и чтение файлов
30 мин
40 Запись данных в файлы
30 мин
41 Контекстные менеджеры (with)
30 мин
42 Работа с форматом JSON
30 мин
43 Модули и импорты
30 мин
44 Полезные модули стандартной библиотеки
30 мин
45 Модуль datetime
30 мин
46 Модуль collections
30 мин
47 Виртуальные окружения (venv)
30 мин
48 Установка сторонних пакетов через pip
30 мин
49 Организация структуры Python-проекта
30 мин
50 Финальный проект: создание приложения
30 мин

Запись данных в файлы

Сохранение результатов работы программы в текстовые документы с использованием режимов w и a.

Прогресс урока: 0%

Введение в персистентность данных и файловые системы. Программирование на языке Python предлагает разработчикам удивительно гибкие и интуитивно понятные инструменты для работы с файловыми системами. Когда мы создаем переменные, списки, кортежи (о которых мы говорили в предыдущих уроках) или словари, все эти структуры данных живут исключительно в оперативной памяти (RAM) вашего компьютера или сервера. Это означает, что как только выполнение скрипта завершается естественным образом, или происходит непредвиденный сбой системы (например, отключение электричества, нехватка памяти или критическая ошибка в коде, вызывающая исключение, которое вы не обработали через try-except), все накопленные, вычисленные и обработанные данные исчезают навсегда. Для того чтобы результаты работы вашей программы сохранились для последующего использования, статистического анализа, передачи другим приложениям или просто для истории, нам необходимо осуществить процесс персистентности — то есть перенести данные из временной (волатильной) оперативной памяти в постоянную (энергонезависимую) память, на жесткий диск (HDD), твердотельный накопитель (SSD) или в сетевое хранилище. В экосистеме языка Python основным мостом, связывающим виртуальное пространство вашей программы и реальную файловую систему операционной системы, является базовая встроенная функция open(). Эта функция не просто абстрактно 'открывает' файл, она обращается к ядру операционной системы (будь то Windows, macOS или Linux) и просит выделить ресурсы для создания специального объекта, который в информатике называется 'файловым дескриптором' или, в контексте ООП в Python, 'файловым объектом'. Этот объект служит своеобразным высокоуровневым пультом управления, через который мы можем отправлять команды операционной системе на чтение, запись или перемещение по массиву байтов на диске. При вызове функции open() критически важно правильно и явно указать так называемый 'режим работы' (mode). Режим работы — это специальный строковый аргумент, который декларирует операционной системе наши намерения относительно файла. В этом уроке мы детально, шаг за шагом, разберем два фундаментальных режима модификации файлов: режим w (от английского 'write' — запись) и режим a (от английского 'append' — добавление). Понимание тонких различий между этими двумя режимами, а также осознание потенциальных рисков, связанных с их неправильным использованием, является обязательным навыком для любого разработчика уровня Intermediate, так как ошибки на этом этапе часто приводят к катастрофической потере данных на рабочих серверах (production).

python
# Базовый пример записи в файл с использованием режима 'w'

# Открываем файл 'hello.txt' в режиме записи ('w')
file_object = open('hello.txt', 'w')

# Записываем строковые данные в объект файла
file_object.write('Привет, мир! Это моя первая запись в файл.')

# КРИТИЧЕСКИ ВАЖНО: всегда закрываем файл после окончания работы,
# чтобы операционная система сбросила буфер на диск и освободила ресурсы.
file_object.close()

Анатомия режима записи 'w' (Write) и его скрытые опасности. Самым базовым и часто используемым режимом для первоначального сохранения данных является режим w. Как мы уже упоминали, это сокращение от английского слова 'write' (писать/записывать). Однако, этот режим таит в себе одну очень важную особенность, о которой часто забывают или которую недооценивают программисты-новички: режим w абсолютно безжалостен, деструктивен и категоричен по отношению к уже существующим данным в вашей файловой системе. Если вы попытаетесь открыть файл (например, с именем report.txt) в режиме w, и этот файл уже физически существует в той же директории, откуда запускается ваш Python-скрипт, интерпретатор Python (а точнее, операционная система по его запросу) сначала полностью и безвозвратно очистит этот файл. Он удалит из него абсолютно всё предыдущее содержимое, обнулив его размер до нуля байт, и только после этого приготовит этот 'чистый лист' для записи ваших новых данных. Это поведение в компьютерных науках называется 'перезаписью с усечением' (file truncation). Представьте, что вы взяли толстую тетрадь с вашими ценными записями, вырвали из нее абсолютно все страницы, вставили новые пустые и только потом начали писать заново. Если же файла с указанным именем в целевой директории не существует, Python любезно создаст для вас абсолютно новый, пустой файл. Понимание этой механики усечения является краеугольным камнем безопасной работы с данными. Случайное использование режима w вместо, например, режима добавления, может привести к катастрофической и безвозвратной потере критически важной информации — например, вы можете случайно затереть файл с базой данных пользователей, логи работы сервера за целый месяц или результаты сложных вычислений, на которые ушли часы процессорного времени. В примере кода выше мы использовали метод write(). Этот метод принимает ровно один аргумент — строку (тип str), которую вы хотите поместить в файл. Важно отметить, что в отличие от всем знакомой функции print(), метод write() не добавляет символ переноса строки автоматически в конце. Если вы вызовете write('A') а затем write('B'), в файле вы увидите слитное 'AB', а не 'A' и 'B' на разных строках. Также в конце примера мы явно вызвали метод close(). Это связано с тем, что запись данных — это дорогостоящая операция для компьютера. Чтобы оптимизировать работу, Python не пишет каждую букву на жесткий диск мгновенно. Он собирает данные в специальной области оперативной памяти — 'буфере'. И только когда буфер заполняется, или когда вы вызываете метод close(), данные физически 'сбрасываются' (flush) на магнитный диск или ячейки SSD. Если ваша программа завершится с ошибкой до вызова close(), данные могут остаться в буфере и никогда не попасть в файл, что приведет к их потере.

Что произойдет, если вы откроете уже существующий файл с важными данными с использованием функции `open('data.txt', 'w')`?

Введите символ (букву), который используется в качестве аргумента mode в функции open() для записи данных с ПЕРЕЗАПИСЬЮ (усечением) файла.

Контекстные менеджеры: оператор with как стандарт индустрии. Как мы уже выяснили в предыдущем блоке, явное закрытие файла с помощью метода close() является критически важным шагом. Оно гарантирует, что буфер данных будет сброшен на физический носитель, а ресурсы операционной системы (такие как дескрипторы файлов, количество которых ограничено в любой ОС) будут освобождены для других программ. Однако в реальном, сложном программировании полагаться на то, что разработчик всегда не забудет написать file.close(), — это плохая практика, нарушающая принципы надежности. Проблема заключается в том, что между моментом открытия файла (open()) и моментом его закрытия (close()) ваш код делает какую-то полезную работу. И во время этой работы может возникнуть исключение (Exception). Например, вы можете попытаться разделить на ноль, обратиться к несуществующему ключу словаря, или может пропасть соединение с базой данных, из которой вы берете информацию для записи. Если в блоке кода до вызова close() произойдет ошибка, выполнение программы прервется (или перейдет в блок except), и строка с закрытием файла никогда не будет выполнена! Файл останется 'висеть' в памяти открытым. Это приводит к утечкам памяти (memory leaks) и блокировкам файлов (например, в Windows вы не сможете удалить или переместить такой файл из Проводника, пока скрипт не завершится полностью). Чтобы изящно и надежно решить эту проблему, в Python (согласно PEP 343) была введена специальная конструкция — контекстный менеджер, который вызывается ключевым словом with. Конструкция with open(...) as file: создает защищенный блок кода. Главная суперспособность контекстного менеджера заключается в том, что он гарантирует 100% закрытие файла при выходе из этого блока кода, независимо от того, как этот выход произошел. Вышли ли вы из блока нормально, завершив все операции, использовали ли оператор return внутри функции, сработал ли цикл break, или, что самое важное, произошло ли внутри блока критическое исключение (ошибка) — в любом из этих сценариев контекстный менеджер автоматически, за кулисами, вызовет метод close() для вашего файла. Использование оператора with при работе с файлами, сетевыми соединениями и базами данных является абсолютным стандартом в Python-сообществе. Если вы пишете код без with (кроме очень специфических случаев асинхронного программирования), на код-ревью (Code Review) старший разработчик (Senior) гарантированно попросит вас переписать этот участок кода. Философия Python (Дзен Python) гласит: 'Явное лучше, чем неявное', но в данном случае безопасность и предотвращение утечек ресурсов ставятся на первое место, делая управление ресурсами автоматическим и надежным.

python
# Профессиональный способ работы с файлами: Контекстный менеджер 'with'

# Переменная f будет ссылаться на файловый объект только внутри блока
with open('user_profile.txt', 'w') as f:
    f.write('Имя: Иван\n')
    f.write('Возраст: 30\n')
    f.write('Статус: Active\n')
    
    # Имитация ошибки в процессе записи
    # result = 10 / 0  # Если раскомментировать, произойдет ZeroDivisionError

# Здесь блок 'with' закончился. 
# Метод f.close() БЫЛ ВЫЗВАН АВТОМАТИЧЕСКИ самим Python.
# Даже если бы строка 10 / 0 вызвала ошибку, файл все равно был бы корректно закрыт.

# Попытка записать данные после блока with вызовет ошибку ValueError:
# f.write('Этот текст вызовет ошибку, так как файл уже закрыт')
print('Запись успешно завершена, файл надежно закрыт.')

Разбор синтаксиса контекстного менеджера и спецсимволы. Давайте внимательно посмотрим на код из предыдущего примера. Синтаксис with open('user_profile.txt', 'w') as f: можно перевести на человеческий язык так: 'Пожалуйста, Python, открой файл user_profile.txt в режиме записи, назови этот открытый объект именем f (от слова file), и пока я нахожусь внутри блока кода с отступом (внутри этого контекста), позволяй мне работать с объектом f. Как только отступ закончится, забери у меня объект f и закрой файл самостоятельно'. Переменная f — это просто имя переменной. Вы могли бы назвать её my_file, document или output_stream, но в Python-сообществе принято использовать короткие имена f или file для простых скриптов. Важно понимать концепцию области видимости (Scope). Сама переменная f технически продолжает существовать в памяти после окончания блока with, но объект, на который она ссылается (открытый файл), переходит в состояние 'закрыт' (closed). Вы можете проверить это, обратившись к атрибуту f.closed, который вернет булево значение True. Любая попытка вызвать f.write() или f.read() после завершения блока with приведет к выбросу исключения ValueError: I/O operation on closed file.

Вторая важная деталь в нашем коде — это использование символа \n внутри строковых литералов (например, 'Имя: Иван\n'). Как мы уже обсуждали, метод write() не добавляет перенос строки автоматически. Если вы хотите, чтобы данные в файле были организованы в виде списка, колонки или просто читабельных абзацев, вы обязаны самостоятельно указывать операционной системе, где строка должна закончиться. Для этого используется так называемая 'эскейп-последовательность' (escape sequence) \n. Обратный слеш (\) говорит интерпретатору Python: 'Внимание: следующий за мной символ — это не просто буква, это специальная команда! Символ 'n' означает 'newline' (новая строка). При записи в файл Python конвертирует эту последовательность в байты, которые ваша операционная система (например, программа Блокнот в Windows или nano в Linux) интерпретирует как команду 'перевести каретку на строку ниже'. В старых версиях Windows исторически использовалась комбинация из двух символов: \r\n (Carriage Return + Line Feed). Однако современный Python очень умен: при открытии файла в стандартном текстовом режиме он использует механизм 'универсальных переносов строк' (Universal Newlines), автоматически транслируя стандартный \n в тот формат, который ожидает ваша текущая операционная система. Таким образом, использование \n делает ваш код кроссплатформенным и надежным.

Задание

Практическое задание: Рефакторинг кода. Ваша задача — переписать устаревший процедурный подход к записи файлов в современный Pythonic-стиль.

  • Ознакомьтесь с устаревшим кодом: f = open('config.txt', 'w'); f.write('debug=True'); f.close()
  • Вспомните синтаксис контекстного менеджера, который мы только что изучили.
  • Напишите ключевое слово with.
  • Используйте функцию open() с нужными аргументами.
  • Используйте ключевое слово as и задайте имя переменной (например, file).
  • Поставьте двоеточие и на новой строке с отступом (4 пробела) вызовите метод .write() у вашей переменной.
  • Убедитесь, что вы удалили явный вызов метода .close(), так как он больше не нужен.
10 баллов

Какое главное преимущество использования конструкции `with open(...) as f:` по сравнению с ручным открытием `f = open(...)` и закрытием `f.close()`?

Режим 'a' (Append) — Добавление данных в конец файла. Теперь, когда мы осознали всю разрушительную мощь режима записи 'w', возникает логичный вопрос: а что делать, если нам нужно вести историю событий? Представьте, что вы разрабатываете телеграм-бота, веб-сервер, или систему сбора метрик с датчиков температуры. Каждую минуту ваша программа получает новые данные. Если вы будете использовать режим w, вы будете постоянно затирать предыдущие показатели, и в файле всегда будет находиться только значение за последнюю минуту. Это совершенно не подходит для систем логирования (веб-аналитика, фиксация ошибок, история транзакций). Для таких задач в Python предусмотрен режим a, что является сокращением от английского 'append' (прикреплять, добавлять в конец). Когда вы открываете файл, используя open('server_logs.txt', 'a'), Python ведет себя совершенно иначе, чем с режимом 'w'. Во-первых, он проверяет существование файла. Если файла нет, режим 'a' поведет себя в точности как режим 'w' — он любезно создаст новый, пустой файл с указанным именем. Но магия начинается, если файл уже существует. Вместо того чтобы безжалостно очищать (усекать) его содержимое, операционная система берет 'курсор' (внутренний указатель позиции в файле, так называемый file pointer) и аккуратно перемещает его в самый конец файла, сразу после последнего записанного байта. Любая последующая операция write() будет добавлять текст в конец, тем самым сохраняя и приумножая все предыдущие записи. Этот режим абсолютно безопасен для существующих данных. Именно благодаря режиму 'a' работают все файлы журналов событий (логи) в мире IT. Когда сервер Nginx записывает информацию о том, кто зашел на ваш сайт, или когда операционная система Windows фиксирует системную ошибку, они под капотом используют механизм, аналогичный режиму 'a'. В сочетании с контекстным менеджером with, режим добавления становится мощнейшим инструментом для создания надежных систем непрерывного сбора данных. Важно помнить: так как мы добавляем данные в конец, нам критически важно не забывать использовать символ переноса строки \n в начале или в конце добавляемой строки, иначе новые данные слипнутся с последним словом предыдущей сессии записи в единую, нечитаемую строку.

python
import datetime

# Создадим простую функцию-логгер с использованием режима 'a' (Append)
def log_action(username, action):
    """
    Функция записывает действия пользователя в файл истории логов.
    Использует режим 'a', чтобы не удалить старые записи.
    """
    # Получаем текущую дату и время для точной фиксации события
    current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    # Формируем строку лога с помощью f-строки (форматирование строк)
    log_message = f"[{current_time}] УЗЕЛ: Пользователь '{username}' выполнил: {action}\n"
    
    # Открываем файл в режиме ДОБАВЛЕНИЯ ('a')
    with open('application_history.log', 'a') as log_file:
        log_file.write(log_message)

# Имитация работы программы во времени
log_action('admin', 'вход в систему')
log_action('guest_99', 'просмотр каталога товаров')
# Если мы вызовем эту функцию завтра, новые записи просто допишутся вниз файла.

Детальный анализ системы логирования и f-строк при работе с файлами. В приведенном выше примере кода мы объединили сразу несколько мощных концепций Python для создания реального, практически применимого инструмента — функции-логгера. Давайте разберем этот архитектурный паттерн подробно. Мы импортировали встроенный модуль datetime, который позволяет нам получать доступ к системным часам. Метод strftime() форматирует этот объект времени в красивую, удобочитаемую строку, что является стандартом (ISO-подобным) для логов. Далее мы используем f-строку (formatted string literal, появившуюся в Python 3.6). F-строки — это лучший друг программиста при формировании текста для записи в файл. Они позволяют внедрять значения переменных (таких как current_time, username, action) непосредственно внутрь строки, обрамляя их фигурными скобками {}. Это делает код в разы более читаемым по сравнению со старым методом конкатенации строк ('[' + time + '] ' + user ...). Обратите особое внимание на символ \n в самом конце f-строки: {action}\n. В контексте режима 'a' (append) это архиважно! Если бы мы забыли этот перенос строки, то при вызове функции три раза подряд, мы бы получили в файле application_history.log нечто вроде: [12:00] admin вошел[12:01] guest посмотрел[12:02] admin вышел. Согласитесь, парсить (программно анализировать) такую сплошную 'кашу' из текста в будущем будет настоящим кошмаром. Добавляя \n, мы гарантируем, что каждая новая порция данных будет начинаться с новой, чистой строки, создавая структуру, похожую на ровную таблицу или список. Также стоит отметить само расширение файла. В примере мы использовали не стандартное .txt, а .log. Для операционной системы нет никакой разницы, как вы назовете файл — это просто набор байтов. Расширение файла в данном случае служит семантической подсказкой для разработчиков и системных администраторов. Увидев файл application_history.log, любой IT-специалист сразу поймет, что внутри находятся системные записи, добавляемые последовательно, и для их просмотра лучше использовать специализированные утилиты (например, команду tail -f в Linux, которая позволяет следить за обновлением файла в реальном времени, что работает именно благодаря режиму добавления 'a').

Проблема типизации данных при записи: Почему файлы 'любят' только строки. Мы уже научились открывать файлы, безопасно работать с ними через контекстные менеджеры и понимать разницу между очисткой (w) и добавлением (a). Теперь мы переходим к одной из самых частых причин разочарования (и исключений TypeError) среди тех, кто только начинает работать с файлами в Python. Эта проблема связана со строгой динамической типизацией языка. Текстовый файл на жестком диске (если мы открываем его в стандартном текстовом режиме, а не в бинарном 'wb') по своей природе является одномерной, плоской последовательностью символов (символов, закодированных в байты). Он не понимает, что такое 'число', 'список', 'вложенный словарь' или 'объект класса'. Для текстового файла всё является просто текстом. Метод write() в Python спроектирован в строгом соответствии с этой концепцией. Метод write() принимает в качестве аргумента исключительно данные типа строка (str). Если в процессе вычислений ваша программа высчитала итоговую сумму покупок пользователя, и она равна, скажем, 1500 (тип данных int — целое число), и вы попытаетесь выполнить код file.write(1500), интерпретатор Python немедленно остановит программу и выбросит исключение: TypeError: write() argument must be str, not int. Python явно говорит вам: 'Я не знаю, как записать число в текст. Должен ли я записать его как четыре символа 1, 5, 0, 0? Или как-то иначе? Укажи это явно!'. Философия 'Дзена Python' (Явное лучше, чем неявное) здесь проявляется во всей красе. Вы обязаны самостоятельно преобразовать любые структуры данных в строковый формат перед тем, как отдать их метоту write(). Для простых чисел и булевых значений самым очевидным решением является использование встроенной функции приведения типов str(): file.write(str(1500)). Однако, для более сложных и комплексных вычислений, приведение через str() становится громоздким. Намного более элегантным, профессиональным и рекомендуемым (Pythonic) подходом является использование f-строк (генерация форматированных строк), о которых мы упоминали ранее. Например: file.write(f'Итоговая сумма: {total_sum} руб.\n'). Внутри фигурных скобок f-строки Python автоматически и безопасно конвертирует любую переменную (включая числа, списки и объекты, вызывая их внутренний магический метод __str__) в строковое представление перед тем, как собрать финальную строку для записи. Это не только решает проблему TypeError, но и позволяет вам контролировать формат вывода (например, округлять числа с плавающей точкой float прямо во время интерполяции: {price:.2f}).

python
# Демонстрация проблемы типизации и её элегантного решения

user_age = 25
user_scores = [85, 90, 95]
is_active = True

with open('analytics.txt', 'w') as f:
    # ОШИБКА: f.write(user_age) -> TypeError!
    
    # Решение 1: Прямое приведение типов функцией str() (менее удобно)
    f.write("Age: " + str(user_age) + "\n")
    
    # Решение 2: Использование методов строк .format() (старый стиль)
    f.write("Scores: {}\n".format(user_scores))
    
    # Решение 3: ПРОФЕССИОНАЛЬНЫЙ ПОДХОД (f-строки)
    # f-строки автоматически вызывают строковое представление для любых типов данных
    f.write(f"Status Active: {is_active}\n")
    f.write(f"Average Score: {sum(user_scores) / len(user_scores):.1f}\n")

# Файл 'analytics.txt' будет успешно создан и заполнен текстом.

Какой тип данных (в Python) является ЕДИНСТВЕННО допустимым аргументом для метода `write()` при работе с файлом в текстовом режиме (режимы 'w' или 'a')?

Глобальная проблема: Кодировки (Encoding) и параметр 'utf-8'. Если вы будете использовать примеры кода, показанные выше (с открытием файла только с параметрами имени файла и режима: open('file.txt', 'w')), на операционных системах семейства Linux или macOS (Apple), скорее всего, всё будет работать идеально, включая запись текста на русском, китайском языках или эмодзи. Однако, если вы (или пользователь вашей программы) запустите этот же самый код на операционной системе Windows, вы с высокой долей вероятности столкнетесь с серьезной архитектурной проблемой, известной в народе как 'кракозябры', а технически — как проблема кодировки (encoding problem) или даже с критической ошибкой UnicodeEncodeError при попытке записать нестандартный символ. Причина кроется в истории развития операционных систем. Компьютер на фундаментальном уровне, на уровне железа, не понимает букв, он понимает только байты (наборы нулей и единиц). Кодировка — это словарь или таблица перевода, которая говорит процессору: 'байт с номером 97 — это английская буква a, а байт с номером 208 — это начало русской буквы Р'. Исторически ОС Windows, особенно её русскоязычные версии, по умолчанию используют устаревшую, так называемую 'региональную' кодировку `cp1251` (Cyrillic) для текстовых файлов в консоли. В то время как Linux и Mac уже давно перешли на универсальный мировой стандарт — `UTF-8` (Unicode Transformation Format), который способен кодировать абсолютно любой символ любого языка мира. Когда в Python вы вызываете open() без явного указания кодировки, интерпретатор запрашивает у операционной системы: 'Какая кодировка у тебя установлена по умолчанию?'. На Windows он получит ответ cp1251. Если в этот момент ваша программа попытается записать в файл современный символ Unicode (например, смайлик 🚀 или специфический символ другого языка), кодировка cp1251 не найдет этого символа в своей узкой таблице и выбросит исключение, обрушив программу. Чтобы сделать ваш код профессиональным, предсказуемым (работающим одинаково на компьютерах всех разработчиков в вашей команде, независимо от их ОС) и защищенным от ошибок Unicode, ВСЕГДА явно передавайте именованный аргумент encoding='utf-8' в функцию open(). Это правило должно стать для вас безусловным рефлексом. Пишете текст на русском? Используйте utf-8. Пишете системный лог? Используйте utf-8. Это гарантирует, что байты на жестком диске будут сформированы по мировому стандарту, и любой современный текстовый редактор (от VS Code до Notepad++) сможет прочитать ваш файл корректно.

python
# Профессиональный стандарт открытия файлов с явным указанием кодировки

ru_text = "Этот текст содержит кириллицу и спецсимволы: 🐍🚀"

# ВСЕГДА добавляйте encoding='utf-8' при работе с текстом!
# Это защитит вас от ошибок UnicodeEncodeError на Windows.
with open('multilingual_data.txt', 'w', encoding='utf-8') as file:
    file.write("Заголовок: Работа с кодировками\n")
    file.write("--------------------------------\n")
    file.write(ru_text + "\n")
    file.write("Успешно сохранено в формате UTF-8.\n")

# Данный код отработает 100% идентично на Windows, Linux и Mac.

Какое значение нужно передать именованному параметру encoding в функции open(), чтобы гарантировать корректную запись кириллицы (русского языка) и работу кода на всех операционных системах?

Метод writelines() — Запись коллекций строк. До сих пор мы рассматривали ситуации, когда данные в файл записывались построчно, строка за строкой, путем многократных вызовов метода write(). Это отлично работает, если данные генерируются последовательно (например, в цикле for или while). Однако, бывают архитектурные сценарии, когда в процессе работы вашей программы вы уже накопили целый массив текстовых данных в оперативной памяти — например, у вас есть готовый список (list) из сотен строк логов или кортеж (tuple) с абзацами текста, сгенерированными искусственным интеллектом. Вызывать метод write() в цикле for для каждого элемента этого списка: for line in my_list: file.write(line) — это абсолютно рабочий, корректный подход, который часто используется. Но Python предлагает специальный, оптимизированный метод для таких случаев — writelines(). Метод writelines(iterable) принимает в качестве аргумента любой итерируемый объект (список, кортеж, множество, или даже генератор), элементы которого обязательно должны быть строками. Функция берет эту коллекцию и записывает все её элементы в файл. Это может показаться очень удобным инструментом, который автоматически превратит ваш список в красивый текстовый файл, но здесь кроется один из самых больших 'подводных камней' Python для новичков! Название метода writelines (записать линии/строки) является крайне обманчивым. Интуитивно кажется, что этот метод самостоятельно добавит символ переноса строки (\n) между элементами списка. Но это не так! Метод writelines() просто берет строки из вашего списка и 'склеивает' их вместе, записывая в файл единым сплошным потоком байтов (абсолютно так же, как если бы вы использовали ''.join(my_list)). Если ваш список выглядит как ['Яблоко', 'Банан', 'Апельсин'], после вызова writelines() в файле окажется монолитное слово ЯблокоБананАпельсин без пробелов и переносов. Поэтому, чтобы метод writelines() работал так, как от него интуитивно ожидают (каждый элемент на новой строке), вы должны заранее подготовить данные. Каждая строка в вашей коллекции уже должна содержать символ \n в конце. Если его там нет, вам придется добавить его на лету. В современных реалиях Python, с появлением List Comprehensions (генераторов списков) и мощных генераторных выражений, подготовка данных для writelines() выглядит изящно и лаконично, что мы продемонстрируем в следующем примере кода. Понимание разницы между write() и writelines() позволяет писать более оптимизированный код, так как передача большой коллекции сразу в метод C-уровня (коим является writelines под капотом) может быть немного быстрее, чем вызов write() тысячи раз из Python-цикла.

python
# Демонстрация работы метода writelines() и его особенностей

# У нас есть сырой список покупок без переносов строк
shopping_list = ["Молоко", "Хлеб", "Сыр", "Яйца"]

with open('shopping_list.txt', 'w', encoding='utf-8') as f:
    # ПЛОХОЙ ПРИМЕР (запишет всё в одну сплошную строку 'МолокоХлебСырЯйца'):
    # f.writelines(shopping_list)
    
    # ПРАВИЛЬНЫЙ ПРИМЕР (используем List Comprehension для добавления \n):
    # Создаем на лету новый список: ['Молоко\n', 'Хлеб\n', ...]
    prepared_list = [item + "\n" for item in shopping_list]
    
    # Передаем подготовленный список в writelines
    f.writelines(prepared_list)
    
    # АЛЬТЕРНАТИВНЫЙ (и часто более простой) способ с использованием метода join():
    # f.write('\n'.join(shopping_list) + '\n')

Задание

Практическое применение генераторов списков (List Comprehensions) перед записью в файл. Это классическое упражнение для уровня Intermediate.

  • Представьте, что у вас есть список чисел: numbers = [10, 20, 30, 40].
  • Вы хотите записать каждое число на новой строке в файл 'numbers.txt'.
  • Вспомните, что writelines() принимает только строки, а у вас числа (int).
  • Вспомните, что writelines() не добавляет \n.
  • Напишите List Comprehension, который решает обе проблемы: [str(num) + '\n' for num in numbers].
  • Откройте файл с помощью with open('numbers.txt', 'w') as file:
  • Передайте результат вашего генератора в file.writelines().
10 баллов

Управление директориями и ошибки FileNotFound / PermissionError. До сих пор во всех наших примерах мы указывали только имя файла, например, open('data.txt', 'w'). Когда вы передаете только имя файла (относительный путь), операционная система ищет или создает этот файл в 'Текущей рабочей директории' (Current Working Directory - CWD). Чаще всего это та самая папка (директория), из которой вы запустили свой Python-скрипт через терминал или вашу среду разработки (IDE, такую как PyCharm или VS Code). Это удобно для простых задач, но в реальных приложениях архитектура значительно сложнее. Часто логи нужно сохранять в папку /var/log/, конфигурации — в /etc/, а результаты вычислений — в отдельную папку reports/ внутри вашего проекта. В функцию open() вы можете передать не только имя, но и полный абсолютный путь (например, C:/Users/Admin/Documents/my_project/reports/data.txt) или сложный относительный путь (например, ../reports/data.txt — что означает 'подняться на папку выше, зайти в reports и там создать файл'). И вот здесь возникает несколько серьезных точек отказа, которые вы, как разработчик, обязаны учитывать. Первая и самая распространенная ошибка — FileNotFoundError. Вы можете подумать: 'Стоп, мы же учили, что режим 'w' автоматически создает файл, если его нет! Откуда взяться ошибке File Not Found?'. Это правда, режим 'w' создает несуществующий файл, но он не создает несуществующие папки (директории) в пути к этому файлу! Если вы напишете open('reports/annual/data.txt', 'w'), а папок reports и annual физически не существует на вашем диске, ОС Windows или Linux откажется выполнять операцию. Операционная система не может положить файл в пустоту. В результате Python прервет выполнение с ошибкой FileNotFoundError: [Errno 2] No such file or directory: 'reports/annual/data.txt'. Вторая критическая ошибка — PermissionError (ошибка прав доступа). Операционные системы многопользовательские. Если ваш скрипт пытается создать файл в системной папке (например, в корне диска C:/ на Windows или в /usr/bin/ на Linux), для записи в которую требуются права Администратора (root), а вы запустили скрипт от имени обычного пользователя, операционная система заблокирует эту попытку в целях безопасности. Python сообщит об этом, выбросив PermissionError: [Errno 13] Permission denied. Аналогичная ошибка произойдет, если вы попытаетесь открыть на запись ('w' или 'a') файл, который в данный момент уже открыт и заблокирован другой программой (например, экселем) или имеет атрибут 'Только для чтения' (Read-only) в свойствах файловой системы. Профессиональный код должен предвидеть эти ситуации, использовать модули os или pathlib для предварительной проверки (или создания) папок перед вызовом open(), а также оборачивать работу с файлами в блоки обработки исключений try...except для элегантного перехвата ошибок доступа.

Какой результат выполнения кода `open('C:/new_folder/data.txt', 'w')`, если папки 'new_folder' на диске 'C:' физически не существует?

Резюме и связь с будущими уроками: От плоского текста к структурированным данным (JSON/CSV). Подводя итоги данного глубокого погружения в тему записи файлов, давайте структурируем наши знания. Мы изучили, что функция open() — это врата в файловую систему ОС. Мы осознали критическую разницу между деструктивным режимом перезаписи w (удобным для экспорта финальных результатов с нуля) и безопасным режимом добавления a (идеальным для ведения логов и накопления исторических данных). Мы поняли, что контекстный менеджер with — это не просто синтаксический сахар, а жизненно важный механизм предотвращения утечек ресурсов и блокировок файлов в случае возникновения непредвиденных исключений (Exceptions) в ходе выполнения программы. Мы разобрались с 'подводными камнями' метода writelines(), который не добавляет переносы строк, и научились решать проблему типизации, используя современный синтаксис f-строк для безопасного перевода любых данных в строковый формат (str) перед записью. И, наконец, мы заучили как мантру необходимость явного указания параметра encoding='utf-8' для предотвращения кракозябр и падений программы на ОС Windows при работе с не-ASCII символами (кириллицей).

Однако, несмотря на всю мощь режимов w и a, запись данных в виде простого, плоского текста имеет огромный архитектурный недостаток. Представьте, что вы успешно записали сложный словарь Python со вложенными списками в текстовый файл с помощью преобразования str(my_dict). Файл сохранен, всё отлично. Но как завтра, запустив другой скрипт, вы планируете прочитать эти данные обратно? Если вы попытаетесь прочитать этот текст, вы получите просто гигантскую строку. Вы не сможете обратиться к ключам словаря (например, text_data['user_id']), потому что для Python это больше не словарь — это просто набор букв, скобочек и запятых. Парсить (восстанавливать структуру) такой текст вручную с помощью регулярных выражений или строковых методов — это адский и нестабильный труд. Именно поэтому в реальной разработке (Backend, Data Science, парсинг сайтов) простой текстовый формат практически никогда не используется для хранения сложных структур данных. Вместо него используются специализированные форматы сериализации (превращения объектов в строку с сохранением структуры) и десериализации. Самыми популярными в веб-разработке являются форматы JSON (JavaScript Object Notation) и CSV (Comma-Separated Values). В следующих уроках мы изучим встроенные модули Python json и csv. Но магия заключается в том, что эти модули под капотом используют абсолютно те же самые функции open(), те же самые контекстные менеджеры with и те же самые режимы w и a, которые мы досконально изучили сегодня! Модуль JSON просто берет на себя сложную работу по форматированию вашего словаря в правильный текст перед вызовом знакомого нам write(). Таким образом, знания, полученные в этом модуле, являются железобетонным фундаментом для всей вашей будущей работы с базами данных, сетевыми API и сложными файловыми хранилищами.