Данные, которые переживают перезапуск программы, и ошибки, которые не роняют её
with open(...) с явной кодировкойpathlibopen(путь, режим, encoding=...) открывает файл и возвращает объект файла. Через него читают и пишут, а после работы файл закрывают методом close: только тогда данные гарантированно записаны на диск.
| Режим | Что делает |
|---|---|
"r" (по умолчанию) | Чтение; файла нет — FileNotFoundError |
"w" | Запись с нуля: существующий файл очищается |
"a" | Дописывание в конец |
"x" | Создать новый; если файл уже есть — ошибка, ничего не стирается |
"rb", "wb" | Двоичный режим: байты, без кодировки — для картинок, архивов |
>>> f = open("notes.txt", "w", encoding="utf-8")
>>> f.write("Первая строка\n")
14
>>> f.write("Вторая строка\n")
14
>>> f.close()
>>> with open("notes.txt", "a", encoding="utf-8") as f:
... f.write("Третья строка\n")
...
14
>>> with open("notes.txt", encoding="utf-8") as f:
... text = f.read()
...
>>> text
'Первая строка\nВторая строка\nТретья строка\n'
>>> print(text, end="")
Первая строка
Вторая строка
Третья строка
write возвращает число записанных символов и сам перевод строки не добавляет — \n пишут явно. Конструкция with open(...) as f: закрывает файл сама, когда блок заканчивается, даже если внутри случилась ошибка. Пишите всегда так; ручной close в примере — только чтобы показать, что происходит.
f.read() читает весь файл в одну строку — удобно для небольших файлов. Большой журнал читают по строкам циклом for line in f: в памяти в каждый момент только одна строка, и программа справится с файлом в гигабайты.
>>> with open("notes.txt", encoding="utf-8") as f:
... for line in f:
... print(repr(line))
...
'Первая строка\n'
'Вторая строка\n'
'Третья строка\n'
>>> with open("notes.txt", encoding="utf-8") as f:
... lines = [line.rstrip("\n") for line in f]
...
>>> lines
['Первая строка', 'Вторая строка', 'Третья строка']
>>> open("nofile.txt", encoding="utf-8")
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
open("nofile.txt", encoding="utf-8")
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'nofile.txt'
repr показывает, что каждая строка заканчивается \n. Его почти всегда убирают: line.rstrip("\n") или line.strip(), если и пробелы по краям не нужны. Открытие несуществующего файла — исключение FileNotFoundError; как его обработать — в разделе 05.
Файл на диске хранит байты, а не буквы. Кодировка — правило перевода букв в байты. Для русского текста встречаются две: UTF-8 — стандарт для Linux, веба и современных программ; cp1251 (Windows-1251) — старые программы Windows и выгрузки из учётных систем.
>>> with open("win.txt", "w", encoding="cp1251") as f:
... f.write("Привет из Windows\n")
...
18
>>> open("win.txt", encoding="utf-8").read()
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
open("win.txt", encoding="utf-8").read()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "<frozen codecs>", line 325, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte
>>> open("win.txt", encoding="cp1251").read()
'Привет из Windows\n'
>>> open("win.txt", "rb").read()
b'\xcf\xf0\xe8\xe2\xe5\xf2 \xe8\xe7 Windows\n'
Файл в cp1251, прочитанный как UTF-8, вызывает UnicodeDecodeError. Прочитанный в верной кодировке — нормальный текст. Байты \xcf\xf0… — та же фраза «как она лежит на диске»: в cp1251 каждая русская буква — один байт, в UTF-8 — два.
encoding="utf-8". Без него Python берёт кодировку системы: в Linux это UTF-8, а в Windows в версиях до 3.15 — cp1251. Программа, которая работала у вас, сломается на компьютере коллеги. Если источник присылает файлы в cp1251 — читайте с encoding="cp1251" и сохраняйте результат уже в UTF-8.Модуль pathlib представляет путь объектом Path. Части пути соединяются оператором / — и правильный разделитель подставляется сам: / в Linux, \ в Windows.
>>> from pathlib import Path
>>> p = Path("reports") / "2026" / "october.txt"
>>> p
PosixPath('reports/2026/october.txt')
>>> p.name, p.stem, p.suffix
('october.txt', 'october', '.txt')
>>> p.parent
PosixPath('reports/2026')
>>> p.exists()
False
>>> p.parent.mkdir(parents=True, exist_ok=True)
>>> p.write_text("Отчёт за октябрь\n", encoding="utf-8")
17
>>> p.exists()
True
>>> p.read_text(encoding="utf-8")
'Отчёт за октябрь\n'
>>> sorted(f.name for f in Path(".").glob("*.txt"))
['notes.txt', 'win.txt']
| Что нужно | Как |
|---|---|
| Имя, имя без расширения, расширение, родитель | p.name, p.stem, p.suffix, p.parent |
| Существует? Файл? Каталог? | p.exists(), p.is_file(), p.is_dir() |
| Создать каталог со всеми родителями | p.mkdir(parents=True, exist_ok=True) |
| Прочитать / записать файл целиком | p.read_text(encoding=…), p.write_text(текст, encoding=…) |
| Файлы по шаблону; во всех подкаталогах | p.glob("*.txt"); p.rglob("*.txt") |
| Домашний каталог пользователя | Path.home() |
Относительный путь "reports" отсчитывается от текущего каталога — того, из которого запустили программу, а не того, где лежит файл .py. Если программа должна найти файл рядом с собой, путь строят от Path(__file__).parent.
Исключение, которое никто не перехватил, останавливает программу. Блок try позволяет перехватить его и продолжить: если в try возникло исключение указанного типа, выполняется блок except.
>>> int("сорок")
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int("сорок")
~~~^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'сорок'
>>> try:
... n = int("сорок")
... except ValueError:
... print("Это не число")
...
Это не число
>>> try:
... result = 10 / int("0")
... except ValueError:
... print("не число")
... except ZeroDivisionError as e:
... print("деление на ноль:", e)
... else:
... print("результат", result)
... finally:
... print("проверка закончена")
...
деление на ноль: division by zero
проверка закончена
Блоков except может быть несколько — для разных типов. as e даёт доступ к самому исключению: print(e) выводит его текст. else выполняется, если исключения не было; finally — в любом случае, им освобождают ресурсы.
Главное применение — проверка ввода. Функция спрашивает, пока не получит правильное число:
def ask_int(prompt: str, low: int, high: int) -> int:
"""Спрашивает целое число в диапазоне, пока не введут правильное."""
while True:
text = input(prompt)
try:
value = int(text)
except ValueError:
print(f" «{text}» — не целое число")
continue
if low <= value <= high:
return value
print(f" нужно от {low} до {high}")
age = ask_int("Возраст: ", 14, 99)
print("Принято:", age)
$ python3 ask_int.py
Возраст: семнадцать
«семнадцать» — не целое число
Возраст: 7
нужно от 14 до 99
Возраст: 17.5
«17.5» — не целое число
Возраст: 17
Принято: 17
except: без типа или except Exception: pass прячет любые ошибки — в том числе опечатки в вашем коде, — и программа молча делает не то.Функция, получившая неправильные данные, не должна печатать «ошибка» и возвращать что попало. Она выбрасывает исключение командой raise — а решает, что делать, вызывающий код:
def parse_score(text: str) -> int:
"""Балл 0–100 из строки; иначе ValueError с понятным текстом."""
value = int(text)
if not 0 <= value <= 100:
raise ValueError(f"балл вне диапазона 0–100: {value}")
return value
for raw in ["87", "120", "abc"]:
try:
print(raw, "->", parse_score(raw))
except ValueError as e:
print(raw, "-> ошибка:", e)
$ python3 raise_demo.py
87 -> 87
120 -> ошибка: балл вне диапазона 0–100: 120
abc -> ошибка: invalid literal for int() with base 10: 'abc'
ValueError — стандартный тип для «значение не подходит». Сообщение говорит, что именно не так и с каким значением: по такому тексту ошибку находят без отладчика. int("abc") внутри функции выбросил своё ValueError, и один и тот же except обработал оба случая.
CSV — таблица в текстовом файле: строка файла — строка таблицы, значения разделены символом. Русские Excel и LibreOffice по умолчанию сохраняют CSV с разделителем ;, потому что запятая занята дробными числами. Ведомость students.csv:
ФИО;Группа;Тест 1;Тест 2;Проект
Анисимова Анна;21В;18;17;45
Борисов Борис;21В;12;;38
Волкова Вера;22Р;20;19;50
Глебов Глеб;22Р;9;11;н/я
Дмитриева Дарья;21В;15;16;41
В данных — обычные проблемы реальных таблиц: пустая ячейка у Борисова и «н/я» вместо числа у Глебова. Разбирать CSV через split(";") нельзя: значение в кавычках может содержать сам разделитель. Модуль csv делает это правильно. DictReader берёт первую строку как заголовки и выдаёт каждую строку словарём:
import csv
totals = {}
problems = []
with open("students.csv", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f, delimiter=";")
for row in reader:
try:
score = sum(int(row[k] or 0) for k in ("Тест 1", "Тест 2", "Проект"))
except ValueError:
problems.append(row["ФИО"])
continue
totals[row["ФИО"]] = (row["Группа"], score)
with open("result.csv", "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f, delimiter=";")
writer.writerow(["ФИО", "Группа", "Итого"])
for name, (group, score) in sorted(totals.items()):
writer.writerow([name, group, score])
print("Записано строк:", len(totals))
print("Нужно проверить вручную:", ", ".join(problems))
$ python3 csv_report.py
Записано строк: 4
Нужно проверить вручную: Глебов Глеб
>>> print(open("result.csv", encoding="utf-8-sig").read(), end="")
ФИО;Группа;Итого
Анисимова Анна;21В;80
Борисов Борис;21В;50
Волкова Вера;22Р;89
Дмитриева Дарья;21В;72
newline="" при открытии — так требует модуль csv, иначе в Windows появляются пустые строки между записями.delimiter=";" — для файлов из русского Excel и для файлов, которые в нём откроют.encoding="utf-8-sig" при записи — UTF-8 с меткой BOM в начале файла. По ней Excel понимает, что файл в UTF-8; без неё кириллица в Excel откроется «кракозябрами».row[k] or 0 превращает пустую ячейку в 0 (пустая строка ложна). «н/я» вызвал ValueError, и строка ушла в список на ручную проверку — программа не упала и не подставила молча ноль за неявку.
JSON — текстовый формат для вложенных данных: объекты ({}), массивы ([]), строки, числа, true/false, null. Он почти совпадает с записью словарей и списков Python, читается человеком и понимается любым языком. В нём хранят настройки, данные небольших программ, ответы веб-сервисов.
>>> import json
>>> settings = {"город": "Санкт-Петербург", "порог": 55, "группы": ["21В", "22Р"], "архив": None}
>>> print(json.dumps(settings))
{"\u0433\u043e\u0440\u043e\u0434": "\u0421\u0430\u043d\u043a\u0442-\u041f\u0435\u0442\u0435\u0440\u0431\u0443\u0440\u0433", "\u043f\u043e\u0440\u043e\u0433": 55, "\u0433\u0440\u0443\u043f\u043f\u044b": ["21\u0412", "22\u0420"], "\u0430\u0440\u0445\u0438\u0432": null}
>>> print(json.dumps(settings, ensure_ascii=False, indent=2))
{
"город": "Санкт-Петербург",
"порог": 55,
"группы": [
"21В",
"22Р"
],
"архив": null
}
>>> with open("settings.json", "w", encoding="utf-8") as f:
... json.dump(settings, f, ensure_ascii=False, indent=2)
...
>>> with open("settings.json", encoding="utf-8") as f:
... loaded = json.load(f)
...
>>> loaded == settings
True
>>> loaded["группы"][1]
'22Р'
>>> try:
... json.loads('{"порог": 55,}')
... except json.JSONDecodeError as e:
... print("Ошибка JSON:", e)
...
Ошибка JSON: Illegal trailing comma before end of object: line 1 column 13 (char 12)
dumps / loads работают со строкой, dump / load — с файлом. Без ensure_ascii=False кириллица записывается кодами г… — корректно, но нечитаемо. indent=2 раскладывает данные по строкам. JSON строже Python: только двойные кавычки, без запятой после последнего элемента, None пишется как null.
Программа заметок, которая хранит их между запусками:
"""Заметки с хранением в JSON."""
import json
import sys
from pathlib import Path
DB = Path("notes.json")
def load() -> list[str]:
if not DB.exists():
return []
try:
return json.loads(DB.read_text(encoding="utf-8"))
except json.JSONDecodeError:
sys.exit(f"Файл {DB} повреждён — исправьте или удалите его")
def save(notes: list[str]) -> None:
DB.write_text(json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8")
notes = load()
if len(sys.argv) > 1:
notes.append(" ".join(sys.argv[1:]))
save(notes)
for i, note in enumerate(notes, 1):
print(f"{i}. {note}")
$ python3 notes_app.py Купить хлеб
1. Купить хлеб
$ python3 notes_app.py Сдать отчёт до пятницы
1. Купить хлеб
2. Сдать отчёт до пятницы
$ python3 notes_app.py
1. Купить хлеб
2. Сдать отчёт до пятницы
sys.argv — список аргументов командной строки, sys.argv[0] — имя программы (подробно — в модуле 08). sys.exit("текст") выводит текст в поток ошибок и завершает программу с кодом 1. Если файл повреждён — например, запись оборвалась на середине, — программа говорит об этом, а не показывает пользователю traceback:
$ python3 notes_app.py
Файл notes.json повреждён — исправьте или удалите его
[код 1]
with open(путь, режим, encoding="utf-8") as f: — всегда с with и явной кодировкой; "w" стирает файлfor line in f, затем line.rstrip("\n")UnicodeDecodeError на русском тексте — файл в другой кодировке, обычно cp1251pathlib.Path; относительный путь — от текущего каталогаtry/except — только конкретный тип и только там, где ошибку можно исправить; свою ошибку — raise ValueError("что не так")csv, newline="", delimiter=";", для Excel utf-8-sig; JSON — ensure_ascii=False, indent=2