Python: полный мануал с примерами и справочником функций
Полный практический мануал Python: от установки и синтаксиса до файлов, сети, тестов и проектов. 25 глав, справочник функций и методов, упражнения с решениями и 195 проверенных примеров.

Python удобно изучать на задачах: посчитать расходы, разобрать текст, прочитать файл, собрать отчёт. В этом руководстве пройдём путь от установки и первых выражений до функций, классов, сети, тестов и самостоятельных проектов.
Выберите одну из 25 глав или найдите нужную функцию через поиск. В справочнике собраны все 73 позиции официальной таблицы встроенных функций и типов Python 3.15, а также 161 метод и 12 свойств строк, коллекций и путей. Для каждой позиции есть назначение, важный нюанс и ссылка на первичную документацию.
Код можно копировать и скачивать. 195 самостоятельных примеров проверены запуском на Python 3.15.0; у упражнений есть решения и ожидаемый результат. Новые возможности языка отмечены в тексте, а интерактивные фрагменты и команды установки объяснены отдельно.
Если начинаете с нуля, открывайте главы по порядку. Если решаете конкретную задачу, ищите по-русски или по имени Python: open, str.split, Path.read_text.
Все главы руководства 25
Все главы на одной странице. Для печати используйте команду печати браузера; для чтения с телефона удобнее открывать главы отдельно.
0. Как пользоваться руководством и что умеет Python
Начните с небольшой программы, которую можно полностью объяснить. В ней достаточно нескольких строк: сохранить данные, выполнить расчёт и показать ответ. Такое начало помогает отличить понимание языка от умения запустить большой чужой проект. В этом руководстве основной проверенный интерпретатор — Python 3.15.0. Версия важна: новое имя или синтаксис может отсутствовать в Python, который уже стоит на вашем компьютере.
Первый результат: код и вывод
Сохраните следующий текст в файле hello.py и запустите его способом из главы об установке. Не добавляйте в файл обозначения вывода и стрелки приглашения терминала. Имя name связывается со строкой, lessons — с числом. Вызов print() отправляет текст в стандартный вывод: в обычном запуске он виден в терминале.
name = "Анна"
lessons = 3
print(f"Привет, {name}!")
print(f"Сегодня пройдено тем: {lessons}")
Привет, Анна! Сегодня пройдено тем: 3
Измените имя и количество тем, затем запустите файл заново. Ответ должен измениться вместе с данными. Скрипт не продолжает старое вычисление: новый запуск снова выполняет его инструкции. Если вы отредактировали файл, но видите прежний ответ, проверьте, сохранили ли изменения и запускаете ли именно этот файл. Такая проверка часто экономит время в начале обучения.
Python, CPython и ваша программа
Python — язык с правилами записи и поведения программ. CPython — его основная реализация, которую вы обычно скачиваете с python.org. Интерпретатор читает программу и выполняет её согласно этим правилам. Другие реализации могут отличаться скоростью, средой и поддержкой возможностей; для повседневного обучения важно сначала знать, какой интерпретатор выбран вашим редактором.
Расширение .py сообщает человеку и инструментам тип файла, но само не устанавливает Python и не выбирает окружение. Сохранённый файл может лежать в правильной папке, а команда python — запускать другое установленное окружение. Поэтому проверка версии и пути sys.executable входит в начало каждого проекта, а не только в первоначальную установку.
Где Python полезен и что нужно дополнительно
Python подходит для обработки текстов и таблиц, подготовки отчётов, автоматизации файловых операций, серверных приложений и научных расчётов. Например, программа может проверить строки выгрузки, сгруппировать заявки по статусам и записать результат в CSV. Язык даёт условия, циклы и функции; конкретные форматы и сетевые протоколы обслуживают библиотеки.
Различайте три уровня. Встроенная функция len() доступна сразу. Модуль стандартной библиотеки csv обычно входит в установленный Python, но его нужно импортировать. Сторонний пакет ставится отдельно в окружение проекта. Ошибка ModuleNotFoundError у стороннего пакета не означает, что язык сломан: пакет может просто отсутствовать в выбранном окружении.
Python не создаёт автоматически красивый интерфейс, точную модель данных или безопасные правила обработки чужого ввода. Для сайта понадобится веб-фреймворк либо собственная серверная логика, для сложных расчётов — подходящие алгоритмы, для базы — продуманная структура и запросы. Выбирайте инструмент по задаче: короткая автоматизация и крупный публичный сервис требуют разного объёма работы.
Скрипт, REPL и ячейки
Скрипт удобен, когда результат нужно повторить: исходник сохранён, запуск выполняет весь файл. REPL — интерактивная оболочка для коротких проверок. Введите выражение, получите значение, попробуйте следующее. Приглашение >>> принадлежит оболочке и не копируется в скрипт. Для выхода обычно используют exit(); сочетания клавиш зависят от системы и терминала.
В блокнотах отдельные ячейки могут выполняться в произвольном порядке и хранить значения от прошлых запусков. Это удобно для исследования, но создаёт ловушку: программа выглядит законченной, хотя новая среда ещё не содержит нужного имени. Перед передачей результата перезапустите среду и выполните ячейки последовательно либо вынесите основной расчёт в обычный скрипт.
Как выбрать маршрут по руководству
Если начинаете с нуля, идите от установки к синтаксису, числам, строкам и коллекциям. После условий и функций попробуйте маленькую задачу с файлами. Не требуйте от себя запоминания всех методов. Полезнее уметь сформулировать действие: найти подстроку, отсортировать записи, прочитать JSON. По такому действию проще найти подходящую главу и справочное имя.
Если задача уже есть, сначала опишите её вход и желаемый результат. Для списка заказов это могут быть строки с ценой и количеством, для отчёта — суммарная стоимость и список ошибок. Затем разбейте путь на шаги: прочитать, проверить, преобразовать, сохранить. Обращайтесь к конкретным главам по мере необходимости; справочник функций дополняет объяснение, но не заменяет проверку примера на ваших данных.
Как читать результат и ошибку
Стандартный вывод показывает то, что программа явно напечатала. Возвращаемое значение функции — другой механизм: его можно передать следующему вычислению, вообще не выводя на экран. Ошибка сообщает, на каком действии выполнение остановилось. У необработанного исключения смотрите последнюю строку с типом и смыслом, затем ближайшую относящуюся к вашему файлу строку traceback.
text = "не число"
try:
count = int(text)
except ValueError as error:
print(type(error).__name__)
print("Количество должно быть записано цифрами")
ValueError Количество должно быть записано цифрами
Здесь ошибка перехвачена намеренно, чтобы скрипт закончился понятным сообщением. В дальнейших главах разберём, где исключение нужно обрабатывать, а где оно помогает обнаружить ошибку разработчика. Пока не пытайтесь подавлять всё подряд. Сначала уменьшите пример до нескольких строк и проверьте входное значение: причина часто становится видна без сложной настройки редактора.
Упражнение: понятный отчёт о результате
Измените первый пример: задайте две переменные, planned и finished, вычислите количество оставшихся тем и напечатайте его. Проверьте случай, когда все темы уже пройдены. Ожидаемый результат — ноль оставшихся тем, а не сообщение об ошибке. Объясните своими словами, где программа хранит число и где превращает его в текст.
Решение упражнения
planned = 5
finished = 5
remaining = planned - finished
print(f"Осталось тем: {remaining}")
Осталось тем: 0
Первичные ориентиры: официальный учебник Python и страница загрузки. Проверенная версия относится к состоянию руководства на 12 октября 2026 года; для другого установленного Python сверяйте пометки рядом с новыми возможностями.
Первичные источники главы:
1. Установка, терминал и отдельное окружение проекта
Хороший результат установки — вы знаете, какой Python выполняет файл и куда попадёт пакет. Наличие значка редактора или работающая команда pip этого ещё не доказывает. Сначала выберите интерпретатор, затем создайте папку учебного проекта и отдельное окружение. Команды в этой главе предназначены для терминала; не вставляйте их в Python-файл.
Windows: менеджер установок и выбор версии
Для актуального Windows официальный путь описан через Python Install Manager. Получите его с python.org или через предложенный там Microsoft Store. Менеджер устанавливает версии Python и предоставляет команды запуска. Старый Python Launcher тоже использует имя py, поэтому на уже настроенном компьютере это имя может принадлежать ему. Команда pymanager явно обращается к новому менеджеру.
pymanager install 3.15
pymanager list
py -V:3.15 --version
Эти команды соответственно устанавливают выбранную ветку, показывают доступные локальные интерпретаторы и проверяют конкретную версию. После установки обычный python может выбирать версию по окружению и настройкам. Используйте явный выбор, если на компьютере несколько Python. При конфликте py начните с pymanager help и проверки команд, а не с удаления всех существующих установок. Подробности: официальное руководство для Windows.
macOS и Linux: сохранить системное окружение
На macOS можно использовать официальный установщик с python.org. После установки проверьте python3 --version и путь выбранного интерпретатора. На Linux Python часто поставляется пакетным менеджером дистрибутива; пакет для создания виртуальных окружений может устанавливаться отдельно. Конкретные названия и версии зависят от дистрибутива. Если его Python отличается от 3.15, простые примеры часто сохраняют смысл, но новые возможности потребуют подходящей отдельной версии.
Не заменяйте системный интерпретатор Linux ради учебного проекта: им могут пользоваться системные инструменты. Создавайте своё окружение выбранным Python. Здесь команды для Unix-систем используют python3; это соглашение командной строки, а не название отдельного языка. На macOS и Linux сначала проверьте установленную версию, затем продолжайте создание проекта.
Файл, редактор и рабочая папка
Создайте папку проекта и файл hello.py в ней. Откройте терминал в этой папке или перейдите туда командой cd. Сохраните файл как обычный UTF-8 текст. Редактор помогает подсветкой и подсказками, терминал запускает команду, REPL выполняет введённые выражения. Программа, написанная в редакторе, не запускается сама после сохранения.
import sys
print("Файл запущен")
print("Python 3.15 или новее:", sys.version_info >= (3, 15))
print("Путь интерпретатора указан:", bool(sys.executable))
Файл запущен Python 3.15 или новее: True Путь интерпретатора указан: True
py -V:3.15 hello.py
# macOS / Linux:
python3 hello.py
Для диагностики можно выполнить python -c "import sys; print(sys.executable)". Путь должен соответствовать тому окружению, которым вы собираетесь работать. Он зависит от компьютера, поэтому не сравнивайте его с чужим примером посимвольно. Если редактор запускает файл иначе, выберите в редакторе этот же интерпретатор; название настройки зависит от редактора.
Отдельное окружение проекта
Виртуальное окружение отделяет пакеты одного проекта от пакетов другого. Создайте его нужным базовым интерпретатором; папка .venv — удобное соглашение, но название может быть другим. Окружение не заменяет исходники программы. Обычно его не пересылают целиком и не сохраняют как переносимую копию проекта: на другой машине окружение создают заново.
py -V:3.15 -m venv .venv
.\.venv\Scripts\python.exe hello.py
python3 -m venv .venv
.venv/bin/python hello.py
Запуск явным путём работает без активации. Активация лишь делает команды текущего терминала удобнее: первым выбирается Python окружения. В PowerShell это .\.venv\Scripts\Activate.ps1, в bash/zsh — source .venv/bin/activate. Если политика PowerShell запрещает скрипт активации, для учебного запуска достаточно явного пути; менять общесистемную политику не требуется. Подробнее: модуль venv.
Установка пакета именно в выбранный Python
Обращение python -m pip запускает pip через этот Python. Отдельная команда pip может принадлежать другому окружению. Поэтому перед установкой проверьте путь и версию pip, затем используйте тот же интерпретатор для запуска программы. Для примеров этой главы сторонние пакеты не нужны; установка понадобится, когда проект действительно выберет внешнюю библиотеку.
.\.venv\Scripts\python.exe -m pip --version
.\.venv\Scripts\python.exe -m pip list
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
Последняя команда ожидает существующий файл требований вашего проекта. Не запускайте её с произвольным найденным в интернете списком. Выбирайте официальный пакет под задачу и фиксируйте используемые версии. pip freeze может сохранить текущие версии окружения, включая зависимости, но такой снимок не гарантирует одинаковую установку на всех операционных системах и версиях Python.
Почему относительный путь может не находить файл
Относительный путь отсчитывается от текущей рабочей папки процесса, а не автоматически от папки исходника. Один и тот же hello.py можно запустить из разных мест. Проверьте Path.cwd(), если программа не находит файл, который вы видите рядом с кодом. Учебный пример ниже получает свой небольшой файл через предоставленный набор данных и не читает настоящие документы пользователя.
from pathlib import Path
file = Path("message.txt")
print(file.read_text(encoding="utf-8").strip())
print("Полный путь абсолютный:", file.resolve().is_absolute())
Привет из файла Полный путь абсолютный: True
Проверка перед поиском сложной причины
Если команда не найдена, проверьте, установлен ли менеджер или интерпретатор и доступна ли команда в новом терминале. Если версия неожиданная, проверьте явный выбор и активное окружение. Если пакет не импортируется, сравните Python, которым выполнена установка, с Python, которым запущена программа. Если ошибка говорит, что файл не найден, проверьте имя, расширение и рабочую папку. Эти причины проще проверять отдельно, чем переустанавливать редактор.
Упражнение: запуск без активации
Создайте .venv, запустите первый пример явным путём и затем вызовите pip через этот же путь. Объясните, какая часть команды выбирает интерпретатор, а какая является аргументом запуска. Для воспроизводимого проекта запишите версию Python и храните исходник отдельно от окружения.
Решение упражнения
В Windows выбор интерпретатора — .\.venv\Scripts\python.exe, на macOS/Linux — .venv/bin/python. После него hello.py задаёт файл, а -m pip задаёт модуль. Окружение можно закрыть вместе с терминалом или выйти из активации командой deactivate; исходник остаётся в папке проекта.
2. Синтаксис, имена, значения и операторы
Читайте программу как последовательность действий над значениями. Сначала выясните, какие имена уже определены, затем посмотрите, какие выражения вычисляются и что изменяется. Одна строка может только вычислять значение, другая — связывать его с именем, третья — выбирать дальнейший путь. Это различие помогает понять код раньше, чем вы запомните все операторы.
Имя связано с объектом
Запись quantity = 3 связывает имя с числом. Знак = здесь не означает математическое равенство и не запускает постоянное пересчитывание: правая часть вычисляется в момент выполнения строки. Если позже изменить quantity, ранее посчитанный итог автоматически не обновится. Для нового итога нужно снова выполнить выражение.
Несколько имён могут ссылаться на один объект. Это особенно заметно со списками и словарями, которые изменяются на месте. Присваивание другому имени не создаёт копию. При этом переназначение имени и изменение самого объекта — разные действия. Полезно проговаривать их отдельно: «это имя теперь обозначает другой список» и «в общий список добавлена запись».
Блоки, отступы и комментарии
После заголовка блока с двоеточием идут строки с отступом: так записываются ветки условий, циклы и тела функций. Обычно используют четыре пробела. Не смешивайте пробелы и табуляцию в одном отступе. Редактор может визуально выровнять разные символы, а интерпретатор всё равно обнаружит несогласованную структуру.
Комментарий начинается с # и заканчивается вместе со строкой. Объясняйте им причину решения, а не очевидное действие каждой инструкции. Docstring — строка в начале модуля, функции или класса; инструменты используют её как описание объекта. Это не отдельный оператор комментария, хотя тройные кавычки позволяют удобно записать несколько строк текста.
Вычисление и порядок действий
Операторы +, -, *, /, //, % и ** выполняют арифметику. Скобки делают порядок понятнее. В условии сравнение == проверяет равенство значений, а != — различие. Для диапазона удобно 0 <= quantity <= 10: цепочка сравнивает соседние значения, среднее выражение вычисляется один раз.
price = 1500
quantity = 3
delivery = 250
discount = 500
total = price * quantity + delivery - discount
print("Итого:", total)
print("Количество допустимо:", 0 <= quantity <= 10)
Итого: 4250 Количество допустимо: True
Не используйте приоритет операторов как головоломку для читателя. Если выражение совмещает расчёт, сравнение и логическую проверку, разделите его на осмысленные шаги. Например, сначала вычислите итог, затем проверьте лимит. Так проще вывести промежуточное значение при поиске ошибки и отдельно проверить граничный случай.
Истинность и короткое вычисление
В условии пустые строки, списки и словари, нулевые числа, None и False обычно считаются ложными. Это называется проверкой истинности. Для непустого списка достаточно if items:; сравнение items == True решает другую задачу и не означает «список содержит данные».
and и or возвращают один из операндов, а не обязательно bool. Второй операнд вычисляется только при необходимости. Выражение nickname or "Гость" удобно для пустой строки, но также заменит ноль, пустой список и другие ложные значения. Если ноль допустим, проверяйте именно None либо отдельно сформулированное правило.
nickname = ""
print(nickname or "Гость")
count = 0
print(count or 10)
print(count if count is not None else 10)
print(bool([]), bool([0]))
print(isinstance(True, int), type(True) is int)
Гость 10 0 False True True False
bool является подтипом int, поэтому isinstance(True, int) истинно. При проверке количества подумайте, принимает ли ваш договор True вместо числа. Для точного встроенного типа подходит type(value) is int; для полиморфных объектов такая строгость может быть лишней. Выбор определяется контрактом функции, а не универсальным правилом для любого ввода.
Непустой текст "False" также истинен: bool проверяет наличие содержимого, а не переводит слово в логическое значение. Если настройка приходит строкой, сначала определите допустимые обозначения, например «да» и «нет», затем разберите их явно. Нельзя считать любую непустую строку подтверждением действия только потому, что условие Python сочло её истинной.
Равенство и идентичность
== сравнивает значения по правилам типа. is проверяет, один ли это объект. Два отдельно созданных списка могут содержать одинаковые элементы, но не быть одним списком. Для числа или строки проверяйте значение через ==. Повторное использование некоторых неизменяемых объектов интерпретатором не является подходящей основой для бизнес-логики.
first = ["черновик"]
same = first
equal = ["черновик"]
print(first == equal, first is equal)
print(first is same)
same.append("проверено")
print(first)
print(equal)
True False True ['черновик', 'проверено'] ['черновик']
Для отсутствия значения используйте value is None. None — отдельный объект, а не пустая строка, ноль или слово «нет». Вопрос «значение не передано?» может отличаться от «передано пустое значение». Когда это различие важно, дальше пригодится отдельный sentinel. Членство in проверяет наличие элемента; для словаря оно относится к ключам.
Распаковка и присваивание в выражении
Распаковка name, count = record распределяет значения по именам. Без звёздочки число элементов должно совпасть: слишком мало или много элементов дадут ValueError. Запись first, *rest = values собирает остальные элементы в список. Это выразительнее ручных индексов, когда структура записи заранее понятна.
Оператор :=, доступный с Python 3.8, связывает имя прямо внутри выражения. Он полезен, когда один результат нужен и для проверки, и для следующего действия. Не используйте его только ради сокращения числа строк: сложное условие с несколькими присваиваниями труднее читать. Обычная отдельная строка остаётся хорошим вариантом.
for title in ("Код", "Руководство", ""):
if (length := len(title)) > 5:
print(title, length)
Руководство 11
Упражнение: сохранить допустимый ноль
Есть значения [None, 0, 4]. Для отсутствующего значения подставьте 10, а ноль оставьте нулём. Напечатайте итоговый список. Затем объясните, почему value or 10 не подходит. Упражнение проверяет смысл условия, а не только знание короткого синтаксиса.
Решение упражнения
values = [None, 0, 4]
prepared = [10 if value is None else value for value in values]
print(prepared)
[10, 0, 4]
Формальные правила сравнения и вычисления: выражения Python. Короткие примеры проверяйте отдельно, если сомневаетесь в поведении типа или границе условия.
3. Числа, округление и точные расчёты
Тип числа выбирают по смыслу результата. Количество записей обычно целое, измерение может быть приблизительным, а расчёт по десятичным правилам требует заранее выбранного округления. Не начинайте с формата красивой строки: сначала определите, какая точность допустима и где производится округление. Формат вывода сам по себе не делает исходное вычисление точным.
Целые числа и три разных деления
int хранит целые числа и не ограничивается обычным 32-битным диапазоном, хотя большие числа требуют памяти и времени. / выполняет обычное деление и для двух целых даёт float. // округляет частное вниз, к минус бесконечности, а не просто отбрасывает дробную часть. Поэтому -7 // 3 равно −3.
Остаток % согласован с целочисленным делением: a == (a // b) * b + a % b при ненулевом делителе. Для положительного делителя остаток неотрицателен. Это удобно для распределения количества по коробкам или минут по часам. Деление на ноль вызывает ошибку; проверка пустого набора данных и проверка нулевого делителя — разные необходимые границы.
Преобразование типа тоже является решением. int("12") читает целое из строки, а int("12.0") вызовет ValueError. Промежуточное float() не стоит добавлять только для обхода такой ошибки: int(12.9) молча отбросит дробную часть. У отрицательного числа это усечение к нулю, поэтому int(-3.9) даёт −3, а не округление вниз. Для количества, возраста или индекса проверьте формат входа и диапазон, прежде чем выполнять расчёт. Десятичная запятая в пользовательском тексте также требует заранее выбранного правила разбора.
Почему 0.1 + 0.2 не совпадает с 0.3
Обычный float хранит двоичное приближение. Некоторые простые десятичные дроби не имеют в нём точного представления. Это не ошибка сложения Python и не признак случайного результата. Для приблизительных измерений сравнивайте с осмысленной относительной или абсолютной погрешностью, например через math.isclose(). Погрешность выбирает задача, а не сам факт использования этой функции.
import math
value = 0.1 + 0.2
print(value)
print(value == 0.3)
print(math.isclose(value, 0.3, rel_tol=1e-9, abs_tol=0.0))
print(-7 // 3, -7 % 3)
0.30000000000000004 False True -3 2
Рядом с нулём относительного допуска может оказаться недостаточно; тогда задают подходящий abs_tol. Отдельно обрабатывайте нечисловые результаты вроде NaN, если ваш алгоритм может их получить. Например, сравнение NaN с самим собой не подтверждает равенство. Для диагностики существуют math.isnan() и math.isfinite(); допустимость бесконечности определяется договором расчёта.
Округление и десятичные правила
round() при точной половине использует ближайшее чётное: round(2.5) даёт 2, round(3.5) — 4. Но неожиданное round(2.675, 2) связано ещё и с двоичным представлением исходного float. Это два разных вопроса: как представлено число и какое правило выбирается для половины. Не исправляйте их добавлением случайной маленькой константы.
Для десятичного расчёта создавайте Decimal из строки. Decimal(0.1) переносит уже существующее двоичное приближение; Decimal("0.1") задаёт нужное десятичное значение. Точность контекста и момент округления всё равно нужно определить. В примере итог округляется один раз до двух знаков по явно выбранному правилу ROUND_HALF_UP.
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("239.90")
quantity = 3
discount = Decimal("0.10")
total = price * quantity * (Decimal("1") - discount)
result = total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(result)
print(round(2.5), round(3.5))
647.73 2 4
Это пример выбранной политики, а не универсальное правило для любой цены или отчёта. Если задача требует округлять каждую строку, итог может отличаться от округления общей суммы. Запишите это требование рядом с расчётом и проверьте пограничные значения. Поведение подробно описано в документации Decimal.
Доли и комплексные числа
Fraction хранит рациональное число как отношение целых. Например, Fraction(1, 3) полезна для точных долей; сумма одной трети и одной шестой равна половине. Создание Fraction из float также наследует его приближение, поэтому выбирайте числитель/знаменатель или подходящую строку. Большие дроби могут быстро увеличить объём вычислений.
complex представляет число с действительной и мнимой частями, например 3 + 4j. У него есть .real, .imag, а abs() даёт модуль. Комплексные числа нельзя упорядочить обычным <: нужен осмысленный критерий, например модуль. Математические функции для них доступны в cmath; обычный math предназначен для действительных аргументов.
Функции math и небольшая выборка
Стандартный math содержит корни, тригонометрические функции и другие численные операции. math.isqrt() вычисляет целую часть квадратного корня целого без промежуточного float. Для простого среднего и медианы есть statistics. Медиана меньше зависит от крайних значений, но сама по себе не объясняет качество выборки: количество наблюдений и смысл пропусков всё ещё важны.
Встроенные abs(), min(), max() и sum() помогают выразить простой расчёт без собственного цикла. sum([]) даёт ноль, но минимум пустой выборки без default вызвать нельзя. Не переносите соглашение одной функции на все остальные. Запишите, что означает отсутствие наблюдений, прежде чем выбирать значение по умолчанию для отчёта.
from fractions import Fraction
from statistics import median, mean
import math
values = [12, 18, 9, 21, 18]
print("Медиана:", median(values))
print(f"Среднее: {mean(values):.1f}")
print("Доля:", Fraction(1, 3) + Fraction(1, 6))
print("Целый корень:", math.isqrt(27))
Медиана: 18 Среднее: 15.6 Доля: 1/2 Целый корень: 5
Воспроизводимая случайность и секреты
random создаёт псевдослучайные последовательности. Для учебной модели удобно отдельное Random(seed): повтор в той же проверяемой среде даёт ту же последовательность и не меняет общий генератор программы. Не превращайте такой seed в пароль. Для непредсказуемых токенов существует secrets; сам токен не должен появляться в журнале учебного теста.
import random
import secrets
first = random.Random(42)
second = random.Random(42)
a = [first.randint(1, 6) for _ in range(5)]
b = [second.randint(1, 6) for _ in range(5)]
print("Повтор совпал:", a == b)
print("Все броски допустимы:", all(1 <= value <= 6 for value in a))
print("Длина hex-токена:", len(secrets.token_hex(4)))
Повтор совпал: True Все броски допустимы: True Длина hex-токена: 8
Упражнение: пустая выборка
Для списка измерений напечатайте медиану, а для пустого списка — «Нет данных». Пустая выборка не должна молча превращаться в нулевое измерение: ноль — отдельное реальное значение. Проверьте оба случая и поясните, почему условие выполняется до вызова статистической функции.
Решение упражнения
from statistics import median
for values in ([], [0, 2, 4]):
if values:
print("Медиана:", median(values))
else:
print("Нет данных")
Нет данных Медиана: 2
4. Строки, Unicode, форматирование и регулярные выражения
Строка хранит текст, но «текст» не всегда означает то, что человек считает одним видимым знаком. При обработке имён, выгрузок и сообщений важно различать символы Unicode, байты файла и формат отображения. Сначала определите договор: какие пробелы можно убрать, какая кодировка используется и какую структуру ожидает следующая часть программы.
Строка неизменяема, индекс считает кодовые точки
str — неизменяемая последовательность Unicode. Индекс text[0] получает элемент, срез text[:3] создаёт строку с выбранным фрагментом. Заменить символ присваиванием text[0] = "А" нельзя: создайте новый текст. Индексация пустой строки вызывает IndexError, а срез спокойно вернёт пустую строку. Выбирайте операцию по ожидаемой границе.
len(str) считает кодовые точки, а не обязательно видимые знаки. Буква с комбинируемым ударением или некоторые эмодзи состоят из нескольких точек. Для сравнения текстов полезна согласованная нормализация через unicodedata.normalize(). Она не означает, что любые визуально похожие знаки становятся одним символом: правила сравнения и допустимый алфавит всё равно нужно определить.
import unicodedata
text = "é"
normalized = unicodedata.normalize("NFC", text)
print(len(text), len(normalized))
data = "Привет".encode("utf-8")
print(type(data).__name__)
print(data.decode("utf-8"))
try:
b"\xff".decode("utf-8")
except UnicodeDecodeError as error:
print(type(error).__name__)
2 1 bytes Привет UnicodeDecodeError
Байты и кодировка
bytes — последовательность чисел 0–255. encode() превращает текст в байты по кодировке, decode() выполняет обратное преобразование. Укажите кодировку формата явно, когда читаете данные. Если байты не соответствуют UTF-8, ошибка помогает обнаружить несогласованный источник. Режим errors="ignore" может незаметно удалить данные; применять его просто для исчезновения сообщения не стоит.
С Python 3.15 UTF-8 используется по умолчанию в соответствующем режиме ввода-вывода, но внешние файлы могут оставаться в другой кодировке. Строка уже не является «UTF-8 строкой»: UTF-8 описывает её байтовую запись. Отделяйте эти уровни в названиях переменных, например payload_bytes и message_text. Это упрощает диагностику двойного кодирования.
Очистка: strip, split и join
strip() без аргумента удаляет крайние пробельные символы. С аргументом он получает набор отдельных символов, а не буквальный суффикс. Для удаления конкретного начала или конца используйте removeprefix() и removesuffix(). Иначе имя файла может сократиться сильнее, чем вы предполагали.
filename = "copy.py"
print(filename.strip(".py"))
print(filename.removesuffix(".py"))
raw = " дизайн разработка практика "
words = raw.split()
print(words)
print(" / ".join(words))
co copy ['дизайн', 'разработка', 'практика'] дизайн / разработка / практика
split() без разделителя объединяет серии пробелов и не создаёт пустых краёв. С явным разделителем поведение другое: "a;;b".split(";") сохраняет пустое поле между точками с запятой. join() соединяет строки выбранным разделителем; числа сначала надо превратить в строки. Для CSV с кавычками берите модуль csv: обычный split не понимает экранирование и разделитель внутри поля.
Поиск, замена и сравнение регистра
Членство part in text удобно, когда нужен только ответ «найдено?». find() возвращает индекс или −1; проверка if text.find(part): ошибочна, потому что нулевой индекс ложный, а −1 истинный. index() вместо −1 вызывает исключение. Выбирайте вариант по тому, требуется ли отсутствие считать обычным результатом или нарушением договора.
replace() создаёт новую строку; старую нужно явно заменить именем результата. Для безрегистрового сравнения Unicode обычно подходит casefold(), который шире простого lower. Перед сравнением можно нормализовать текст, но не удаляйте значимые символы без правила задачи. Имя человека, артикул и поисковый запрос могут требовать разных стратегий.
Форматирование числа и длинного текста
F-строка вычисляет выражения внутри фигурных скобок и сразу создаёт текст. Спецификатор после двоеточия управляет отображением: .2f задаёт два знака после точки, 04d — целое с дополнением нулями до четырёх позиций. Отображение округлённого float не исправляет исходную погрешность. Для десятичных правил используйте подходящий численный тип до форматирования.
Встроенный format(value, ".2f") применяет тот же договор формата к одному значению. Метод строки "{name}: {value:.2f}".format(name="Сумма", value=amount) удобен для заранее подготовленного шаблона. Это разные интерфейсы: format у значения не ищет именованные поля, а метод строки подставляет их в текст. Литерал f-строки вычисляется сразу в текущем коде. Не выбирайте eval для обработки отдельного шаблона: обычно нужен понятный формат и явные данные.
import re
amount = 2490.5
print(f"Сумма: {amount:.2f}")
print(f"Номер: {7:04d}")
for text in ("Заказ-0073", "Заказ-A73", ""):
match = re.fullmatch(r"Заказ-(?P<number>[0-9]+)", text)
print(int(match.group("number")) if match else "Некорректный номер")
Сумма: 2490.50 Номер: 0007 73 Некорректный номер Некорректный номер
Для переноса обычного текста есть textwrap.fill(), для удаления общего отступа многострочной заготовки — textwrap.dedent(). Это текстовые операции, они не заменяют переносы интерфейса браузера. Отдельно учитывайте значимые переводы строк в исходнике: автоматическое выравнивание может изменить смысл стихотворения, кода или адресного блока.
Регулярное выражение и t-строка
Регулярное выражение полезно для небольшой устойчивой формы. fullmatch() проверяет всю строку, search() ищет фрагмент, findall() собирает совпадения. В предыдущем примере используются только ASCII-цифры [0-9]; \d также распознаёт другие Unicode-цифры. Не пытайтесь разбирать произвольный HTML или полноценный CSV одной регуляркой: для структуры выбирайте соответствующий парсер.
Префикс r у строкового литерала удобен для регулярных выражений: он сохраняет обратные косые черты для синтаксиса самого re. Это не отключает специальные символы регулярного выражения. Если нужно искать введённый пользователем текст буквально, подготовьте его через re.escape(); тогда точка или скобка не превратятся в команду шаблона. Не добавляйте escape ко всему готовому выражению, иначе исчезнет и задуманный синтаксис поиска.
Начиная с Python 3.14 t-строка возвращает Template, а не готовую строку. Она отделяет постоянные фрагменты от интерполяций. Обработчик шаблона решает, как собрать конечный результат. Сам синтаксис не экранирует HTML, не параметризует SQL и не обещает безопасную вставку данных. Новая конструкция нужна там, где обработка значений должна быть контролируемой отдельно от записи шаблона.
name = "<Анна>"
template = t"Привет, {name}!"
print(type(template).__name__)
print(template.strings)
print(template.values[0])
Template
('Привет, ', '!')
<Анна>
Упражнение: подготовить список подписей
Есть подписи с крайними пробелами и пустая запись. Уберите только крайние пробелы, исключите пустой результат и соедините оставшееся через запятую с пробелом. Сохраните внутренний пробел у «Новый проект». Ожидаются две подписи без пустой позиции. Это полезный маленький шаг перед формированием меню или отчёта.
Решение упражнения
raw = [" Дизайн ", " ", " Новый проект "]
cleaned = [value.strip() for value in raw]
print(", ".join(value for value in cleaned if value))
Дизайн, Новый проект
Справочные детали: re, Unicode, t-строки и Template.
Методы и свойства str
Все 47 публичных методов, имеющих отдельные сигнатуры str.* на странице Built-in Types.
str.capitalizeМетод · Регистр
Делает первый символ заглавным, а остальные строчными.
Нюанс. Первый символ приводится к titlecase; исходная строка не изменяется.
Официальная документация: str.capitalizestr.casefoldМетод · Регистр
Нормализует регистр для сравнения текста без учёта регистра.
Нюанс. Преобразование сильнее lower(), например немецкое ß превращается в ss.
Официальная документация: str.casefoldstr.centerМетод · Выравнивание
Дополняет строку с двух сторон до заданной ширины.
Нюанс. Если ширина не больше длины, сокращения строки не происходит.
Официальная документация: str.centerstr.countМетод · Поиск
Считает неперекрывающиеся вхождения подстроки.
Нюанс. Для пустой подстроки без границ результат равен длине строки плюс один.
Официальная документация: str.countstr.encodeМетод · Кодировки
Кодирует текст в bytes.
Нюанс. По умолчанию использует UTF-8; errors='ignore' может незаметно потерять данные.
Официальная документация: str.encodestr.endswithМетод · Поиск
Проверяет окончание строки.
Нюанс. Можно передать кортеж допустимых окончаний, но не регулярное выражение.
Официальная документация: str.endswithstr.expandtabsМетод · Выравнивание
Заменяет табуляции пробелами до следующей позиции табуляции.
Нюанс. Количество добавленных пробелов зависит от текущей колонки.
Официальная документация: str.expandtabsstr.findМетод · Поиск
Возвращает позицию первого вхождения подстроки.
Нюанс. Если подстроки нет, возвращает -1; проверять результат как bool нельзя.
Официальная документация: str.findstr.formatМетод · Форматирование
Подставляет значения в поля шаблона в фигурных скобках.
Нюанс. Литералы фигурных скобок в шаблоне записываются как {{ и }}.
Официальная документация: str.formatstr.format_mapМетод · Форматирование
Подставляет именованные значения прямо из переданного отображения.
Нюанс. Отображение не копируется в dict, поэтому могут работать его __missing__ и другие настройки.
Официальная документация: str.format_mapstr.indexМетод · Поиск
Возвращает позицию первого вхождения подстроки.
Нюанс. При отсутствии вызывает ValueError, в отличие от find().
Официальная документация: str.indexstr.isalnumМетод · Проверки
Проверяет, состоит ли непустой текст только из букв и числовых символов.
Нюанс. Использует Unicode, поэтому допустимые символы не ограничены латиницей и ASCII-цифрами.
Официальная документация: str.isalnumstr.isalphaМетод · Проверки
Проверяет, состоит ли непустая строка только из букв Unicode.
Нюанс. Пробелы, дефисы и пустая строка не проходят проверку.
Официальная документация: str.isalphastr.isasciiМетод · Проверки
Проверяет, находятся ли все символы в диапазоне ASCII.
Нюанс. Пустая строка также возвращает True.
Официальная документация: str.isasciistr.isdecimalМетод · Проверки
Проверяет, состоит ли непустая строка из десятичных цифр Unicode.
Нюанс. Знак, точка и запятая не проходят эту проверку.
Официальная документация: str.isdecimalstr.isdigitМетод · Проверки
Проверяет, состоит ли непустая строка из цифр Unicode.
Нюанс. Некоторые принимаемые символы, например надстрочные цифры, нельзя напрямую преобразовать int().
Официальная документация: str.isdigitstr.isidentifierМетод · Проверки
Проверяет синтаксическую допустимость имени Python.
Нюанс. Ключевые слова также могут пройти проверку; дополнительно используйте keyword.iskeyword().
Официальная документация: str.isidentifierstr.islowerМетод · Проверки
Проверяет строчный регистр всех символов, имеющих регистр.
Нюанс. Должен присутствовать хотя бы один символ с регистром; цифры сами по себе не подходят.
Официальная документация: str.islowerstr.isnumericМетод · Проверки
Проверяет, состоит ли непустая строка из числовых символов Unicode.
Нюанс. Допускает символы дробей, поэтому это не проверка пригодности для float() или int().
Официальная документация: str.isnumericstr.isprintableМетод · Проверки
Проверяет пригодность символов для неэкранированного представления repr().
Нюанс. Пустая строка проходит проверку; наличие переноса строки приводит к False.
Официальная документация: str.isprintablestr.isspaceМетод · Проверки
Проверяет, состоит ли непустая строка только из пробельных символов.
Нюанс. Считает пробелами также табуляции и переводы строк.
Официальная документация: str.isspacestr.istitleМетод · Проверки
Проверяет заглавное начало и строчное продолжение групп букв.
Нюанс. Это алгоритм Unicode, а не проверка правил написания заголовков на конкретном языке.
Официальная документация: str.istitlestr.isupperМетод · Проверки
Проверяет верхний регистр всех символов, имеющих регистр.
Нюанс. Нужен хотя бы один символ с регистром, поэтому строка из цифр возвращает False.
Официальная документация: str.isupperstr.joinМетод · Соединение
Соединяет строки из iterable с выбранным разделителем.
Нюанс. Все элементы должны быть str; числа и bytes автоматически не преобразуются.
Официальная документация: str.joinstr.ljustМетод · Выравнивание
Дополняет строку справа до заданной ширины.
Нюанс. Короткая width не обрезает строку.
Официальная документация: str.ljuststr.lowerМетод · Регистр
Переводит символы с регистром в нижний регистр.
Нюанс. Для сравнения без учёта регистра на разных языках casefold() обычно полнее.
Официальная документация: str.lowerstr.lstripМетод · Очистка
Удаляет разрешённые символы слева.
Нюанс. chars обозначает набор отдельных символов, а не точный префикс.
Официальная документация: str.lstripstr.maketransМетод · Замены
Создаёт таблицу для посимвольного преобразования translate().
Нюанс. Вариант с двумя строками требует их одинаковой длины; None задаёт удаление символа.
Официальная документация: str.maketransstr.partitionМетод · Разделение
Делит строку по первому разделителю на три части.
Нюанс. Разделитель остаётся средней частью; при его отсутствии результат равен (строка, '', '').
Официальная документация: str.partitionstr.removeprefixМетод · Очистка
Удаляет один точный префикс, если он присутствует.
Нюанс. Не работает как lstrip(): удаляет целую последовательность только в начале.
Официальная документация: str.removeprefixstr.removesuffixМетод · Очистка
Удаляет один точный суффикс, если он присутствует.
Нюанс. Пустой суффикс оставляет строку без изменений.
Официальная документация: str.removesuffixstr.replaceМетод · Замены
Заменяет вхождения подстроки другим текстом.
Нюанс. count ограничивает число замен; в 3.15 этот аргумент также можно передать по имени.
Официальная документация: str.replacestr.rfindМетод · Поиск
Возвращает позицию последнего вхождения подстроки.
Нюанс. При отсутствии возвращает -1.
Официальная документация: str.rfindstr.rindexМетод · Поиск
Возвращает позицию последнего вхождения подстроки.
Нюанс. При отсутствии вызывает ValueError.
Официальная документация: str.rindexstr.rjustМетод · Выравнивание
Дополняет строку слева до заданной ширины.
Нюанс. Для чисел со знаком и заполнения нулями удобнее zfill().
Официальная документация: str.rjuststr.rpartitionМетод · Разделение
Делит строку по последнему разделителю на три части.
Нюанс. При отсутствии разделителя результат равен ('', '', строка).
Официальная документация: str.rpartitionstr.rsplitМетод · Разделение
Разбивает строку на части, начиная с правого края.
Нюанс. Отличие от split() заметно при ограничении maxsplit.
Официальная документация: str.rsplitstr.rstripМетод · Очистка
Удаляет разрешённые символы справа.
Нюанс. chars обозначает набор символов, а не точный суффикс.
Официальная документация: str.rstripstr.splitМетод · Разделение
Разбивает строку по разделителю или пробельным символам.
Нюанс. split() объединяет серии пробелов, а split(' ') сохраняет пустые части между отдельными пробелами.
Официальная документация: str.splitstr.splitlinesМетод · Разделение
Разбивает текст по границам строк.
Нюанс. Завершающий перевод строки не создаёт лишнюю пустую строку; keepends сохраняет окончания.
Официальная документация: str.splitlinesstr.startswithМетод · Поиск
Проверяет начало строки.
Нюанс. Поддерживает кортеж префиксов; start и end ограничивают область проверки.
Официальная документация: str.startswithstr.stripМетод · Очистка
Удаляет разрешённые символы с обоих краёв.
Нюанс. Аргумент chars является набором символов; для точного префикса или суффикса нужны другие методы.
Официальная документация: str.stripstr.swapcaseМетод · Регистр
Меняет верхний регистр на нижний и наоборот.
Нюанс. Двойное применение не всегда восстанавливает исходный Unicode-текст.
Официальная документация: str.swapcasestr.titleМетод · Регистр
Делает начало каждой группы букв заглавным.
Нюанс. Апостроф разделяет группы, поэтому английские сокращения могут оформляться неожиданно.
Официальная документация: str.titlestr.translateМетод · Замены
Преобразует символы по таблице соответствий.
Нюанс. Таблица может заменить символ несколькими символами или удалить его значением None.
Официальная документация: str.translatestr.upperМетод · Регистр
Переводит символы с регистром в верхний регистр.
Нюанс. Unicode-преобразование не гарантирует, что результат пройдёт isupper().
Официальная документация: str.upperstr.zfillМетод · Выравнивание
Дополняет строку нулями слева до заданной ширины.
Нюанс. Начальные + или - остаются перед добавленными нулями.
Официальная документация: str.zfill5. Списки, кортежи и изменение данных
Последовательность нужна, когда важны порядок и положение элементов: шаги инструкции, строки отчёта, карточки каталога. Список удобно изменять, кортеж — использовать как фиксированную запись, range — описывать диапазон целых. Сначала решите, меняется ли набор по ходу работы и нужно ли сохранять исходный порядок. Одинаковый внешний вид квадратных или круглых скобок не означает одинаковый договор данных.
Список и кортеж
list хранит упорядоченные элементы и позволяет добавлять, удалять и заменять их. Элементы могут иметь разные типы, но для обработки обычно проще придерживаться одной понятной структуры. tuple тоже хранит порядок, однако его позиции нельзя переназначить. Одноэлементный кортеж записывается с запятой: (value,). Одни скобки вокруг значения кортеж не создают.
Неизменяемость кортежа не замораживает вложенный список. Если кортеж содержит список, этот список можно изменить. Поэтому кортеж не всегда годится как ключ словаря: все его элементы тоже должны быть хешируемыми. Для записи вроде пары координат из чисел кортеж удобен; для растущего списка задач обычно понятнее list.
Индекс, срез и диапазон
Индексы начинаются с нуля; отрицательный индекс считает с конца. Отсутствующая позиция вызывает IndexError. Срез items[start:stop:step] берёт элементы до stop, не включая границу. Выходящие за края границы среза допустимы. Для списка срез создаёт новый внешний список, но вложенные объекты остаются общими. Проверяйте пустой список перед обращением к первой позиции.
range(start, stop, step) описывает арифметическую последовательность целых и не хранит отдельный список каждого числа. Stop также не включается; шаг не может быть нулём. Для движения назад нужен отрицательный шаг и согласованные границы. range(3) даст 0, 1, 2, а не 1, 2, 3. Для номера позиции в готовом наборе чаще удобнее enumerate.
Добавление, удаление и возвращаемое значение
append(x) добавляет один объект. extend(values) добавляет элементы другого iterable, а insert(index, x) вставляет в указанное место. Если передать список в append, он останется одним вложенным элементом. pop() удаляет и возвращает элемент, remove(x) удаляет первое равное значение и при отсутствии вызывает ValueError. Выбирайте метод по тому, известна позиция или значение.
Методы изменения вроде sort() обычно возвращают None. Поэтому items = items.sort() теряет ссылку на список в этом имени. Сам объект отсортирован, но имя теперь обозначает None. Если нужна новая сортированная последовательность, используйте sorted(). Такое разделение помогает сохранять исходник для повторного сравнения.
items = [3, 1, 2]
result = items.sort()
print(items)
print(result)
items.append([4, 5])
print(items)
items.pop()
items.extend([4, 5])
print(items)
[1, 2, 3] None [1, 2, 3, [4, 5]] [1, 2, 3, 4, 5]
Критерий и стабильная сортировка
Параметр key задаёт значение, по которому сортируются элементы. Для записей это может быть цена или пара «цена, название». Сортировка стабильна: равные ключи сохраняют взаимный порядок исходных элементов. Это удобно, когда сначала выбран важный порядок поступления и его нужно оставить при одинаковых оценках. Reverse меняет направление сортировки; он не превращает произвольный ключ в другой смысл.
records = [
{"name": "Макет", "price": 150},
{"name": "Иконка", "price": 100},
{"name": "Шаблон", "price": 100},
]
ordered = sorted(records, key=lambda record: record["price"])
print([record["name"] for record in ordered])
print([record["name"] for record in records])
['Иконка', 'Шаблон', 'Макет'] ['Макет', 'Иконка', 'Шаблон']
Строки по умолчанию сравниваются по значениям Unicode, а не по любому желаемому языковому порядку. Если задача требует безрегистровой сортировки, задайте подходящий key, например casefold. Для смешанных несовместимых типов вроде числа и словаря обычная сортировка не знает универсального порядка. Лучше нормализовать данные или явно определить критерий.
Копия и общие вложенные объекты
items.copy(), list(items) и срез items[:] создают поверхностную копию списка. Изменение его внешних позиций не изменит исходные позиции, однако оба списка ещё ссылаются на одни вложенные объекты. copy.deepcopy() копирует вложенную структуру по правилам объектов. Используйте глубокую копию по необходимости, а не как обязательную операцию для любого числа или строки.
from copy import deepcopy
original = [[1], [2]]
shallow = original.copy()
deep = deepcopy(original)
shallow[0].append(9)
print(original)
print(deep)
shared_rows = [[]] * 2
shared_rows[0].append("x")
print(shared_rows)
independent_rows = [[] for _ in range(2)]
independent_rows[0].append("x")
print(independent_rows)
[[1, 9], [2]] [[1], [2]] [['x'], ['x']] [['x'], []]
Повторение [[]] * n повторяет ссылки на один внутренний список. Для независимых строк создайте каждую отдельно через comprehension. Та же идея нужна при построении таблицы: решить, должны ли ячейки разделять объект или хранить собственное состояние. Ошибка общего объекта может проявиться только после первого изменения, поэтому проверяйте не только первоначальный вывод.
Comprehension, enumerate и zip
Comprehension удобно описывает небольшое преобразование: [x * x for x in values if x > 0]. Для нескольких побочных действий яснее обычный цикл. Не удаляйте элементы списка одновременно с обходом того же списка: позиции сдвигаются и часть значений можно пропустить. Создайте отфильтрованный результат отдельно либо обходите специально подготовленную копию.
enumerate(items, start=1) даёт номер и элемент. zip(a, b) объединяет наборы по позициям и обычно останавливается на самом коротком. Если длины обязаны совпасть, с Python 3.10 используйте strict=True: несовпадение станет ошибкой при обходе. Сам zip — ленивый объект, поэтому проверка возникает во время потребления, а не обязательно в строке его создания.
names = ["А", "Б"]
counts = [2]
try:
pairs = list(zip(names, counts, strict=True))
except ValueError:
print("Длины не совпали")
for number, name in enumerate(names, start=1):
print(number, name)
Длины не совпали 1 А 2 Б
Bytes, bytearray и представление буфера
bytes неизменяемы, bytearray допускает изменение байтов. Индекс даёт целое число, не односимвольный str. memoryview предоставляет доступ к буферу без обязательного создания отдельной копии его содержимого. Возможность записи зависит от исходного буфера. Эти типы полезны для двоичных форматов; для обычного текста сначала выполните decode с согласованной кодировкой.
data = bytearray(b"ABC")
with memoryview(data) as view:
view[1] = ord("Z")
print(bytes(data))
print("Только чтение:", view.readonly)
data.append(ord("D"))
print(bytes(data))
b'AZC' Только чтение: False b'AZCD'
View обращается к исходному буферу: изменение через него видно в bytearray. После блока with представление освобождено, и исходную последовательность можно увеличить. Пока представление активно, изменение размера такого буфера может быть запрещено. Не обращайтесь к освобождённому view; хранить отдельную постоянную копию следует через bytes, если именно это требуется дальнейшей обработке.
Упражнение: фильтрация без изменения исходника
Из значений [3, -1, 0, 4] создайте список квадратов положительных чисел. Ноль здесь исключён по правилу задачи, исходный список должен остаться прежним. Проверьте также пустой вход. Для этого преобразования достаточно comprehension; временное удаление элементов из исходного списка не требуется.
Решение упражнения
values = [3, -1, 0, 4]
squares = [value * value for value in values if value > 0]
print(squares)
print(values)
print([value * value for value in [] if value > 0])
[9, 16] [3, -1, 0, 4] []
Методы и свойства list
Все 11 методов списка из официального раздела More on Lists; многие методы на странице типов описаны как общие операции последовательностей, поэтому индивидуального list.* якоря у них нет.
list.appendМетод · Добавление
Добавляет один объект в конец списка.
Нюанс. Переданный список станет одним вложенным элементом; метод возвращает None.
Официальная документация: list.appendlist.extendМетод · Добавление
Добавляет все элементы iterable в конец списка.
Нюанс. Строка добавится по символам; метод возвращает None.
Официальная документация: list.extendlist.insertМетод · Добавление
Вставляет объект перед выбранной позицией.
Нюанс. Меняет исходный список и возвращает None.
Официальная документация: list.insertlist.removeМетод · Удаление
Удаляет первое вхождение значения.
Нюанс. Если значения нет, вызывает ValueError.
Официальная документация: list.removelist.popМетод · Удаление
Удаляет и возвращает элемент по индексу.
Нюанс. Без индекса выбирает последний; пустой список вызывает IndexError.
Официальная документация: list.poplist.clearМетод · Удаление
Удаляет все элементы списка.
Нюанс. Меняет существующий объект, поэтому его другие ссылки также увидят пустой список.
Официальная документация: list.clearlist.indexМетод · Поиск
Находит индекс первого вхождения значения.
Нюанс. При отсутствии вызывает ValueError; start не меняет систему отсчёта результата.
Официальная документация: list.indexlist.countМетод · Поиск
Считает элементы, равные заданному значению.
Нюанс. Сравнение выполняется по равенству значений, а не по идентичности объектов.
Официальная документация: list.countlist.sortМетод · Порядок
Сортирует элементы существующего списка.
Нюанс. Возвращает None; для нового списка используется sorted().
Официальная документация: list.sortlist.reverseМетод · Порядок
Разворачивает порядок элементов существующего списка.
Нюанс. Возвращает None и не сортирует значения.
Официальная документация: list.reverselist.copyМетод · Копирование
Создаёт поверхностную копию списка.
Нюанс. Вложенные объекты остаются общими с исходным списком.
Официальная документация: list.copyПервичные источники главы:
6. Словари, множества и хешируемость
Словарь отвечает на вопрос «какое значение связано с этим ключом?», множество — «есть ли такой элемент?». Не выбирайте список только потому, что он уже знаком: поиск записи по идентификатору проще выразить словарём, а проверку уникальности — множеством. Структура данных задаёт договор о повторениях, порядке и допустимых ключах, поэтому её выбор влияет на смысл программы.
Ключи, значения и порядок вставки
dict связывает уникальные ключи со значениями. Присваивание существующему ключу заменяет его значение. В современных Python порядок вставки сохраняется, но это не автоматическая сортировка. Если запись удалена и добавлена снова, она окажется в конце. Для упорядоченного по цене отчёта по-прежнему нужен отдельный критерий сортировки.
Ключ должен быть хешируемым: его hash должен оставаться согласованным с равенством. Строки и числа подходят, список — нет. Кортеж подходит только когда хешируемы его элементы. Значения словаря могут быть изменяемыми. Учтите равенство ключей: 1 и True не создают независимые позиции, а строка "1" — отдельный ключ. Нормализуйте идентификатор до обращения к словарю.
Отсутствующий ключ и переданное None
data[key] при отсутствии вызывает KeyError. get(key, default) возвращает default и не вставляет ключ. Проблема возникает, когда None является допустимым значением: результат get(key) уже не различает отсутствие и явный None. Можно проверить членство, а для общего значения по умолчанию использовать отдельный маркер.
С Python 3.15 доступен встроенный sentinel(). Создайте один объект-маркер и сравнивайте с ним через is. Значение с тем же названием, созданное другим вызовом, не является вашим маркером. Для старых версий простой object() также подходит как внутренний уникальный маркер; новую возможность отмечайте рядом с кодом, а не используйте молча в проекте 3.14.
MISSING = sentinel("MISSING")
options = {"timeout": None, "retries": 0}
for key in ("timeout", "retries", "host"):
value = options.get(key, MISSING)
if value is MISSING:
print(key, "не задан")
else:
print(key, "задан:", value)
timeout задан: None retries задан: 0 host не задан
Представления и изменение словаря
keys(), values() и items() возвращают представления текущего словаря. После изменения словаря представление отражает новые данные. Если нужен независимый список на этот момент, создайте его через list. Не добавляйте и не удаляйте ключи во время обхода самого словаря: изменение размера нарушает итерацию. Подготовьте список нужных ключей или новый результат.
update() изменяет словарь на месте. Оператор left | right, доступный с 3.9, создаёт объединённый словарь; при совпадении ключа побеждает значение справа. Это удобно для конфигурации «базовые настройки плюс выбранные изменения». Порядок наложения должен быть явным: перестановка операндов может незаметно изменить результат.
base = {"theme": "light", "size": 10}
keys = base.keys()
snapshot = list(keys)
base["enabled"] = True
print(snapshot)
print(list(keys))
merged = base | {"size": 20}
print(merged["size"], base["size"])
['theme', 'size'] ['theme', 'size', 'enabled'] 20 10
setdefault и общая ссылка fromkeys
setdefault(key, default) возвращает существующее значение либо вставляет default и возвращает его. Для группы можно вызвать groups.setdefault(name, []).append(value). Помните, что выражение аргумента default вычисляется до вызова даже при уже существующем ключе. Для регулярно создаваемых групп defaultdict(list) часто выражает намерение понятнее.
dict.fromkeys(names, []) даёт каждому ключу одну и ту же ссылку на список. Изменение списка через один ключ проявится у остальных. Для независимых групп используйте {name: [] for name in names}. С неизменяемым общим значением, например None, fromkeys вполне удобен. Ошибка заключается не в методе, а в неверном ожидании отдельных изменяемых объектов.
Множества и хешируемый набор
set хранит уникальные хешируемые элементы. Пустое множество создаётся через set(); фигурные скобки {} создают словарь. Пересечение & находит общее, объединение | — всё, разность - — элементы только слева, симметрическая разность ^ — элементы ровно одного набора. Порядок множества не используйте как порядок отчёта: отсортируйте результат явно.
available = {101, 102, 103}
requested = {102, 104}
print("Доступны:", sorted(available & requested))
print("Отсутствуют:", sorted(requested - available))
key = frozenset({"read", "write"})
permissions = {key: "редактор"}
print(permissions[frozenset({"write", "read"})])
Доступны: [102] Отсутствуют: [104] редактор
frozenset — неизменяемое множество, которое можно использовать как ключ. Оно подходит, когда комбинация независима от порядка: набор разрешений или выбранных признаков. Если порядок важен, кортеж выражает другой договор. Превращение списка во множество удалит повторения; убедитесь, что повторы не являются количеством или повторными событиями, которые нужно сохранить.
Для изменения set есть add(), remove() и discard(). Remove сообщает KeyError при отсутствии элемента, discard спокойно сохраняет множество. Когда отсутствие нормально, второй вариант выражает это без дополнительного условия. pop() множества возвращает произвольный элемент; его нельзя использовать как обещание «самый старый» или «первый по алфавиту». Для порядка обработки выбирайте очередь либо отдельно отсортированный набор.
Counter, defaultdict и очередь
Counter считает повторения, defaultdict создаёт значение при обращении к отсутствующему ключу, deque удобно добавляет и удаляет элементы с обоих концов. Они входят в стандартный модуль collections. У defaultdict обычный get не запускает фабрику. Для очереди используйте popleft, а не удаление первого элемента большого списка со сдвигом остальных.
from collections import Counter, defaultdict, deque
counts = Counter("Код код сайт".casefold().split())
print(sorted(counts.items()))
groups = defaultdict(list)
groups["код"].append("Python")
print(groups["код"])
queue = deque(["проверить", "сохранить"])
print(queue.popleft())
queue.append("отправить")
print(list(queue))
[('код', 2), ('сайт', 1)]
['Python']
проверить
['сохранить', 'отправить']
frozendict: новое в Python 3.15
Встроенный frozendict сохраняет отображение без изменения его позиций после создания. Это отдельный тип, не подкласс dict. Порядок вставки сохраняется, равенство сравнивает содержимое. Хеширование возможно только при хешируемых ключах и значениях. Неизменяемость внешнего отображения не замораживает вложенные объекты: список внутри всё ещё остаётся списком.
config = frozendict({"tags": ["старт"]})
print(isinstance(config, dict))
config["tags"].append("код")
print(config["tags"])
try:
hash(config)
except TypeError:
print("Вложенный список не хешируется")
False ['старт', 'код'] Вложенный список не хешируется
Для неизменяемой конфигурации дополнительно выберите неизменяемые значения, например кортежи. Если нужен изменяемый черновик, создайте dict из готового отображения. Версионные особенности: новые встроенные типы 3.15.
Упражнение: независимые списки по отделам
Создайте отделы «дизайн» и «код» с отдельными списками задач. Добавьте задачу только в дизайн и проверьте, что список кода пуст. Затем напечатайте количество задач по каждому отделу. Не используйте один общий список как значение fromkeys.
Решение упражнения
departments = {name: [] for name in ("дизайн", "код")}
departments["дизайн"].append("обложка")
print({name: len(tasks) for name, tasks in departments.items()})
print(departments["код"])
{'дизайн': 1, 'код': 0}
[]
Методы и свойства dict
Все 11 публичных методов с отдельными сигнатурами dict.*; специальные методы и операторы вынесены за пределы этой карты.
dict.clearМетод · Изменение
Удаляет все пары из словаря.
Нюанс. Возвращает None и изменяет тот же объект словаря.
Официальная документация: dict.cleardict.copyМетод · Копирование
Создаёт поверхностную копию словаря.
Нюанс. Вложенные изменяемые значения остаются общими.
Официальная документация: dict.copydict.fromkeysМетод · Создание
Создаёт словарь с указанными ключами и одним начальным значением.
Нюанс. Один изменяемый value будет общим для всех ключей.
Официальная документация: dict.fromkeysdict.getМетод · Чтение
Возвращает значение ключа или запасное значение.
Нюанс. Не добавляет отсутствующий ключ, в отличие от setdefault().
Официальная документация: dict.getdict.itemsМетод · Перебор
Возвращает представление пар ключ–значение.
Нюанс. Представление отражает изменения словаря; менять размер словаря во время обхода нельзя.
Официальная документация: dict.itemsdict.keysМетод · Перебор
Возвращает представление ключей.
Нюанс. Это динамическое представление; для снимка используют list(d.keys()).
Официальная документация: dict.keysdict.popМетод · Изменение
Удаляет ключ и возвращает его значение.
Нюанс. Отсутствующий ключ вызывает KeyError, если default не указан.
Официальная документация: dict.popdict.popitemМетод · Изменение
Удаляет и возвращает последнюю добавленную пару.
Нюанс. Порядок LIFO гарантирован; пустой словарь вызывает KeyError.
Официальная документация: dict.popitemdict.setdefaultМетод · Изменение
Возвращает значение ключа и при отсутствии добавляет default.
Нюанс. Выражение default вычисляется заранее даже тогда, когда ключ существует.
Официальная документация: dict.setdefaultdict.updateМетод · Изменение
Добавляет пары и перезаписывает совпадающие ключи.
Нюанс. Изменяет словарь и возвращает None; не создаёт результирующий словарь.
Официальная документация: dict.updatedict.valuesМетод · Перебор
Возвращает представление значений.
Нюанс. Это динамическое представление; равенство двух представлений values не сравнивает наборы значений.
Официальная документация: dict.valuesМетоды и свойства set
Все 17 публичных методов с отдельными сигнатурами set.*.
set.isdisjointМетод · Проверки
Проверяет отсутствие общих элементов.
Нюанс. Метод принимает любой iterable, а не только множество.
Официальная документация: set.isdisjointset.issubsetМетод · Проверки
Проверяет, входят ли все элементы в другой набор.
Нюанс. Множество считается подмножеством самого себя.
Официальная документация: set.issubsetset.issupersetМетод · Проверки
Проверяет, содержит ли множество все элементы другого набора.
Нюанс. Равные множества проходят эту проверку.
Официальная документация: set.issupersetset.unionМетод · Новые множества
Создаёт объединение элементов.
Нюанс. Метод принимает iterable; оператор объединения требует множества.
Официальная документация: set.unionset.intersectionМетод · Новые множества
Создаёт множество общих элементов.
Нюанс. Не изменяет исходный набор, в отличие от intersection_update().
Официальная документация: set.intersectionset.differenceМетод · Новые множества
Создаёт множество элементов, отсутствующих в других наборах.
Нюанс. Порядок операндов меняет результат.
Официальная документация: set.differenceset.symmetric_differenceМетод · Новые множества
Создаёт множество элементов, встречающихся только в одном из двух наборов.
Нюанс. Общие элементы исключаются; метод принимает один другой iterable.
Официальная документация: set.symmetric_differenceset.copyМетод · Копирование
Получает поверхностную копию множества.
Нюанс. Копирование не создаёт глубокие копии объектов-элементов.
Официальная документация: set.copyset.updateМетод · Изменение
Добавляет элементы из одного или нескольких iterable.
Нюанс. Изменяет множество и возвращает None.
Официальная документация: set.updateset.intersection_updateМетод · Изменение
Оставляет в множестве только общие элементы.
Нюанс. Изменяет исходный объект и возвращает None.
Официальная документация: set.intersection_updateset.difference_updateМетод · Изменение
Удаляет элементы, встречающиеся в других наборах.
Нюанс. Изменяет исходный объект и возвращает None.
Официальная документация: set.difference_updateset.symmetric_difference_updateМетод · Изменение
Заменяет множество симметрической разностью.
Нюанс. Принимает один другой iterable и меняет исходный объект.
Официальная документация: set.symmetric_difference_updateset.addМетод · Изменение
Добавляет один элемент в множество.
Нюанс. Элемент должен быть хешируемым; повторное добавление не создаёт дубликат.
Официальная документация: set.addset.removeМетод · Изменение
Удаляет заданный элемент.
Нюанс. Отсутствующий элемент вызывает KeyError.
Официальная документация: set.removeset.discardМетод · Изменение
Удаляет элемент, если он присутствует.
Нюанс. Отсутствующий элемент не вызывает исключение.
Официальная документация: set.discardset.popМетод · Изменение
Удаляет и возвращает произвольный элемент.
Нюанс. Выбирать по порядку добавления нельзя; пустое множество вызывает KeyError.
Официальная документация: set.popset.clearМетод · Изменение
Удаляет все элементы множества.
Нюанс. Возвращает None и сохраняет сам объект.
Официальная документация: set.clearМетоды и свойства frozenset
Все 8 публичных методов frozenset; изменяющих методов нет.
frozenset.isdisjointМетод · Проверки
Проверяет отсутствие общих элементов.
Нюанс. Метод принимает любой iterable, а не только множество.
Официальная документация: frozenset.isdisjointfrozenset.issubsetМетод · Проверки
Проверяет, входят ли все элементы в другой набор.
Нюанс. Множество считается подмножеством самого себя.
Официальная документация: frozenset.issubsetfrozenset.issupersetМетод · Проверки
Проверяет, содержит ли множество все элементы другого набора.
Нюанс. Равные множества проходят эту проверку.
Официальная документация: frozenset.issupersetfrozenset.unionМетод · Новые множества
Создаёт объединение элементов.
Нюанс. Метод принимает iterable; оператор объединения требует множества.
Официальная документация: frozenset.unionfrozenset.intersectionМетод · Новые множества
Создаёт множество общих элементов.
Нюанс. Исходное множество неизменно; результат также является frozenset.
Официальная документация: frozenset.intersectionfrozenset.differenceМетод · Новые множества
Создаёт множество элементов, отсутствующих в других наборах.
Нюанс. Порядок операндов меняет результат.
Официальная документация: frozenset.differencefrozenset.symmetric_differenceМетод · Новые множества
Создаёт множество элементов, встречающихся только в одном из двух наборов.
Нюанс. Общие элементы исключаются; метод принимает один другой iterable.
Официальная документация: frozenset.symmetric_differencefrozenset.copyМетод · Копирование
Получает поверхностную копию множества.
Нюанс. Копирование не создаёт глубокие копии объектов-элементов.
Официальная документация: frozenset.copyМетоды и свойства frozendict (Python 3.15)
Шесть методов чтения и создания выведены из официально указанного сходства API dict и frozendict с перечисленными исключениями; непосредственно отдельный новый якорь имеет fromkeys().
frozendict.copyМетод · Копирование
Получает поверхностную копию неизменяемого словаря.
Нюанс. Неизменяемость словаря не делает вложенные значения неизменяемыми.
Официальная документация: frozendict.copyfrozendict.fromkeysМетод · Создание
Создаёт неизменяемый словарь с указанными ключами и общим значением.
Нюанс. Один изменяемый value будет общим для всех ключей; нехешируемое значение лишает frozendict хешируемости.
Официальная документация: frozendict.fromkeysfrozendict.getМетод · Чтение
Возвращает значение ключа или запасное значение.
Нюанс. Запасное значение не добавляет отсутствующий ключ; изменяющего setdefault() у frozendict нет.
Официальная документация: frozendict.getfrozendict.itemsМетод · Перебор
Возвращает представление пар ключ–значение.
Нюанс. Структура словаря неизменна, но сами значения могут быть изменяемыми объектами.
Официальная документация: frozendict.itemsfrozendict.keysМетод · Перебор
Возвращает представление ключей.
Нюанс. Порядок добавления сохраняется; представление можно преобразовать в list.
Официальная документация: frozendict.keysfrozendict.valuesМетод · Перебор
Возвращает представление значений.
Нюанс. Сами значения могут быть изменяемыми; представление не является готовым списком.
Официальная документация: frozendict.valuesПервичные источники главы:
7. Условия, циклы и сопоставление с образцом
Условие выбирает действие, цикл повторяет его для данных, а match сопоставляет структуру. Начинайте с правил задачи: какие значения допустимы, что делать с пропуском и когда обработку можно закончить. Отдельные ясные ветки обычно полезнее сложного выражения, в котором проверка и изменение состояния смешаны.
Условие и выбор одного результата
if проверяет истинность выражения. elif описывает следующие варианты, else — оставшийся. Выполняется первая подходящая ветка. Поэтому порядок условий важен: более общее правило, поставленное раньше, может поглотить специальное. Для диапазонов проверяйте границы отдельно: «до десяти» и «не больше десяти» расходятся ровно на одном значении.
Тернарное выражение "готово" if complete else "в работе" выбирает значение. Оно удобно для небольшого выбора, но длинные вложенные варианты затрудняют чтение. Если у веток несколько действий или собственные проверки, используйте обычный if. Не подменяйте проверку пользовательского ввода assert: в оптимизированном запуске утверждения могут быть отключены.
Цикл по данным, continue и пропуски
for берёт элементы iterable по очереди. Вам обычно не нужно вручную увеличивать индекс. continue пропускает остаток текущей итерации и переходит к следующей. Это удобно для отсутствующей записи, однако условие пропуска должно соответствовать данным: нулевое количество иногда является допустимым значением, а None обозначает отсутствие.
records = [None, 0, 3, -1, 2]
accepted = []
for count in records:
if count is None:
continue
if count < 0:
print("Пропущено отрицательное:", count)
continue
accepted.append(count)
print("Приняты:", accepted)
print("Сумма:", sum(accepted))
Пропущено отрицательное: -1 Приняты: [0, 3, 2] Сумма: 5
Для номера используйте enumerate, для параллельного обхода согласованных последовательностей — zip. Если цикл изменяет ту же коллекцию, по которой идёт, продумайте отдельную стратегию. Новый результирующий список часто проще и надёжнее удаления позиций на месте. Сложный набор фильтров можно вынести в функцию с понятным именем.
Не теряйте информацию о пропущенных записях, если она важна читателю отчёта. Иногда continue должен сопровождаться увеличением счётчика ошибок или сохранением причины отдельно от результата. Пустой результат после фильтрации не обязательно означает, что вход был пуст: все записи могли быть недопустимыми. Различайте эти состояния перед вычислением среднего, выводом «успех» или переходом к следующему шагу.
While: условие завершения и состояние
while повторяется, пока условие истинно. Он подходит, когда заранее неизвестно число шагов, но есть правило завершения. Внутри должен происходить переход, который приближает к завершению: уменьшение оставшегося количества, изменение состояния или получение нового ввода. Если этот переход случайно пропущен из-за continue, получится бесконечный цикл.
Ограничение попыток удобно записывать явно. Не считайте ожидание ресурса обычным вычислительным циклом, который может бесконечно крутиться без паузы. В реальной программе повторные сетевые попытки требуют отдельной политики таймаута и остановки; в учебной задаче сначала проверяйте сам счётчик и ветку последней попытки. Pass ничего не делает и может обозначить временно пустой блок, но не завершает цикл.
remaining = 3
while remaining > 0:
print("Осталось:", remaining)
remaining -= 1
print("Завершено")
Осталось: 3 Осталось: 2 Осталось: 1 Завершено
Break и else у цикла
break заканчивает ближайший цикл. Else у for или while выполняется, если цикл завершился без break. Это удобно для поиска: найденный элемент вызывает break, а else сообщает отсутствие. Else может выполниться и для пустой последовательности, потому что никакого break не было. Исключение или return прерывают дальнейшее выполнение; это не обычное завершение с последующим else.
for names in (["HTML", "CSS", "Python"], []):
for index, name in enumerate(names):
if name == "Python":
print("Найдено на позиции:", index)
break
else:
print("Не найдено")
Найдено на позиции: 2 Не найдено
Break относится только к ближайшему циклу, поэтому во вложенных обходах он не выходит сразу из всех уровней. Часто такое действие проще оформить функцией и вернуть найденный результат. Следите за смыслом слова «найдено»: пустая строка или нулевой индекс могут быть корректным результатом, поэтому не проверяйте их только по истинности.
Match/case: форма данных, а не только равенство
С Python 3.10 match/case сопоставляет значение с образцами. Образец списка может проверить количество элементов и захватить части; образец словаря — требуемые ключи. Дополнительный guard после if проверяет захваченные значения. Выбирается первая подходящая ветка, дальнейшего проваливания в соседние case нет. Конструкция особенно удобна для нескольких форм команды или события.
commands = [["echo", "привет"], ["sum", 2, 3], ["sum", 2], {"action": "ping"}]
for command in commands:
match command:
case ["echo", str(text)]:
print(text)
case ["sum", int(a), int(b)] if type(a) is int and type(b) is int:
print(a + b)
case {"action": "ping"}:
print("pong")
case _:
print("Неподдерживаемая команда")
привет 5 Неподдерживаемая команда pong
Голое имя в case обычно захватывает значение, а не сравнивает его с уже известной переменной. Для констант используют литерал или именованный атрибут вроде Status.READY. Подчёркивание — образец без сохранения значения. Образец int(x) принимает и bool как подтип int; в примере guard отдельно требует точный int. Выбирайте строгость по договору команды.
Короткое преобразование и новое в 3.15
List, set и dict comprehensions описывают построение результата. Читайте их как «взять элементы, при необходимости отфильтровать, получить новые значения». Когда выражение содержит много веток или изменяет внешнее состояние, обычный цикл яснее. У генераторного выражения результат ленивый и последующее выполнение переносится на момент обхода; не принимайте его за уже готовый список.
Python 3.15 добавляет распаковку * и ** в comprehensions. Она позволяет объединить несколько iterable или отображений. В старом интерпретаторе такой синтаксис не выполнится, поэтому у примера ниже стоит версия 3.15+. Обычная вложенная comprehension остаётся вариантом для предыдущих версий и иногда лучше показывает структуру шага.
groups = [[1, 2], [], [3]]
new_way = [*group for group in groups]
classic = [value for group in groups for value in group]
print(new_way)
print(new_way == classic)
[1, 2, 3] True
Упражнение: первое положительное значение
Найдите первое число больше нуля в [0, -2, 7, 4]. Если такого значения нет, напечатайте «Нет подходящего». Повторите поиск для пустого списка. Важно напечатать только первый найденный результат и не сообщить об отсутствии после успешного поиска.
Решение упражнения
for values in ([0, -2, 7, 4], []):
for value in values:
if value > 0:
print(value)
break
else:
print("Нет подходящего")
7 Нет подходящего
Формальные правила: составные инструкции; новые конструкции отдельно описаны в изменениях 3.15.
8. Свои функции, аргументы и области видимости
Функция даёт имя законченной операции: рассчитать строку заказа, подготовить подпись, выбрать подходящие записи. Хорошая граница функции понятна без чтения каждой строки: какие данные она получает, что возвращает и какие ошибки означает недопустимый ввод. Начните с маленького расчёта, который не читает терминал и не меняет внешние данные. Такой код проще повторить, проверить и использовать в другой программе.
Def и return: вернуть результат, затем показать
def создаёт функцию и связывает её с именем. Тело выполняется при вызове, а не при объявлении. return завершает этот вызов и передаёт значение вызывающему коду. Если return отсутствует или записан без значения, результатом будет None. Print отправляет текст в вывод; он не заменяет возвращаемое значение, которое требуется следующему расчёту.
Отделяйте вычисление от способа общения с пользователем. Функция стоимости может возвращать число, а терминал, веб-страница или отчёт затем оформляют его по-своему. Если функция сама спрашивает input и печатает ответ, её труднее использовать в тесте или в другом интерфейсе. Это не запрет на print, а выбор понятной ответственности: ввод, расчёт и представление можно соединить внешним кодом.
При вызове параметр получает ссылку на переданный объект, но его имя является локальным для функции. Переназначение параметра другому объекту не переназначает имя вызывающего кода. Зато изменение общего списка через append видно снаружи. Не описывайте передачу данных одной фразой «всё копируется» или «всё передаётся по ссылке», если она скрывает эту разницу. Для функции, обещающей сохранить исходник, копию создают явно на подходящем уровне: внешнего списка достаточно не для каждой вложенной структуры.
Проверяйте такие обещания отдельным наблюдением. Сохраните исходный список, вызовите функцию и посмотрите на него после вызова, затем сравните возвращённый объект. Если задача намеренно изменяет список на месте, сообщите это в имени и docstring. Если возвращается новый результат, вызывающий код может безопасно сравнить варианты и оставить исходные данные для следующего действия.
def line_total(price: int, quantity: int, *, discount: int = 0) -> int:
"""Возвращает итог строки с целыми суммами."""
if type(price) is not int or price < 0:
raise ValueError("Цена должна быть неотрицательным целым")
if type(quantity) is not int or quantity < 0:
raise ValueError("Количество должно быть неотрицательным целым")
subtotal = price * quantity
if type(discount) is not int or not 0 <= discount <= subtotal:
raise ValueError("Скидка должна быть в пределах суммы")
return subtotal - discount
print(line_total(1500, 3, discount=500))
print(line_total(100, 0))
try:
line_total(100, True)
except ValueError as error:
print(type(error).__name__)
4000 0 ValueError
Здесь аннотации показывают намерение, а настоящая проверка записана явно. Bool специально не принимается как количество. Пустой заказ даёт нулевой итог при нулевой скидке; скидка сверх суммы отклоняется. Эти правила относятся к примеру. В вашем проекте сначала согласуйте договор данных, единицу суммы и политику округления, затем оформляйте их в коде функции.
Возвращать можно любой подходящий объект. Несколько значений обычно упаковывают в кортеж: return count, total, после чего вызывающий код может распаковать результат. Не возвращайте иногда строку ошибки, иногда число, если следующий шаг ожидает только число. Используйте согласованную структуру результата либо понятное исключение; это предотвращает отложенную ошибку на совсем другом действии.
Обрабатывайте ошибку там, где можно принять осмысленное решение. Функция расчёта может отклонить данные, а интерфейс — попросить исправить ввод. Если внутри расчёта перехватить любое Exception и вернуть ноль, вызывающий код уже не отличит корректный нулевой результат от сбоя. Проверяйте ожидаемые нарушения договора явно и сохраняйте неожиданную ошибку для диагностики. Это также помогает тестировать один расчёт без настоящего терминального ввода.
Параметры и способы передачи аргументов
Параметры записаны в определении функции, аргументы передаются при вызове. Позиционные аргументы сопоставляются по порядку, именованные — по имени. Одно значение нельзя одновременно передать одному параметру обоими способами: это TypeError. Именованные аргументы особенно полезны для нескольких флагов или чисел, смысл которых не очевиден по позиции. Сравните мысленно настройку resize(800, 600) с длинной цепочкой переключателей без названий.
Косая черта / отделяет позиционные-only параметры, а * делает последующие параметры именованными-only. Первый вариант полезен, когда имя внутреннего параметра не должно стать частью публичного интерфейса. Второй заставляет указать смысл настройки при вызове. Не усложняйте сигнатуру без задачи: для небольшой собственной функции достаточно привычного сочетания обязательных данных и нескольких именованных настроек.
def label(value, /, *, prefix="Тема"):
return f"{prefix}: {value}"
print(label("Grid", prefix="Раздел"))
try:
label(value="Grid")
except TypeError:
print("value передаётся позиционно")
def add_all(*values):
return sum(values)
print(add_all(2, 3, 4))
print(add_all())
Раздел: Grid value передаётся позиционно 9 0
*args собирает дополнительные позиционные аргументы в кортеж, **kwargs — именованные в словарь. Имена args/kwargs являются соглашением; смысл задают звёздочки. При вызове звёздочки распаковывают iterable и отображение в аргументы. Следите за повтором ключей и числом ожидаемых значений. Свободные kwargs удобны для передачи настроек дальше, но опечатка в параметре может остаться незамеченной, если вы просто игнорируете неизвестные ключи.
def greeting(name, *, formal=False):
prefix = "Здравствуйте" if formal else "Привет"
return f"{prefix}, {name}"
options = {"name": "Анна", "formal": True}
print(greeting(**options))
try:
greeting(**(options | {"unknown": 1}))
except TypeError:
print("Неизвестный параметр отклонён")
Здравствуйте, Анна Неизвестный параметр отклонён
Распаковка настроек не превращает данные в исполняемый исходник: здесь словарь лишь сопоставляется с объявленными параметрами. Но сам договор ключей всё ещё важен. В примере неизвестная настройка отклоняется обычной сигнатурой функции. Если принять свободные kwargs, проверку неизвестных имён придётся сделать самостоятельно либо явно передать их следующему известному обработчику. Такой выбор должен быть частью документации публичного API.
Передавать функцию как значение можно без круглых скобок: handler = label. Запись handler = label(...) вызывает её сейчас и сохраняет результат. Это различие встречается в callback, сортировке и декораторах. Если интерфейс ожидает действие на будущее, передавайте вызываемый объект либо специально подготовленное замыкание, а не случайный результат раннего вызова.
Default вычисляется один раз при определении
Выражение значения по умолчанию вычисляется, когда выполняется def. Оно не вычисляется заново для каждого вызова. С неизменяемым числом или строкой это обычно ожидаемо. Со списком или словарём один объект может накапливать состояние между вызовами. Такая ошибка часто появляется только после повторного использования функции, поэтому первая успешная проверка недостаточна.
def shared(label, bucket=[]):
bucket.append(label)
return bucket
print(shared("А"))
print(shared("Б"))
def independent(label, bucket=None):
if bucket is None:
bucket = []
bucket.append(label)
return bucket
print(independent("А"))
print(independent("Б"))
['А'] ['А', 'Б'] ['А'] ['Б']
Во втором варианте новый список создаётся в теле функции. Однако если вызывающий код явно передаст собственный список, функция изменит именно его. Выберите и опишите этот договор: изменять предоставленную коллекцию или возвращать отдельную. Параметр по умолчанию None исправляет разделённый default, но не делает автоматически любую операцию чистой и не копирует все входные данные.
Тот же принцип касается вызова функции в default: текущее время, случайное значение или прочитанная настройка будут получены при определении. Если они должны меняться каждый раз, вычисляйте их внутри тела. Если значение намеренно фиксируется заранее, default может быть подходящим способом. Сначала определите нужный момент вычисления, затем выбирайте запись.
Когда None имеет собственный смысл, нужен отдельный маркер отсутствия. С Python 3.15 можно создать sentinel. В следующем примере отсутствие аргумента означает обычную настройку, явный None — отключённое ограничение, а ноль остаётся переданным значением. Проверка через or потеряла бы это различие.
UNSET = sentinel("UNSET")
def timeout_label(value=UNSET):
if value is UNSET:
return "По умолчанию"
if value is None:
return "Без ограничения"
return f"Передано: {value}"
print(timeout_label())
print(timeout_label(None))
print(timeout_label(0))
По умолчанию Без ограничения Передано: 0
Область видимости: LEGB, global и nonlocal
Обычный поиск имени часто описывают правилом LEGB: локальная область, окружающие функции, область модуля, встроенные имена. Не путайте «область модуля» с единственным общим пространством всей программы. Каждый модуль имеет свои имена. Название вроде list или sum, которому вы присвоили собственное значение, может заслонить встроенную функцию и неожиданно изменить последующие вызовы.
Если в функции есть обычное присваивание имени, Python обычно считает его локальным во всём этом блоке. Поэтому чтение такого имени до присваивания может вызвать UnboundLocalError, даже если одноимённая переменная есть снаружи. Global направляет присваивание в область модуля. Nonlocal использует уже существующее имя в ближайшей подходящей окружающей функции. Это точные механизмы, а не способы сделать любую переменную доступной отовсюду.
limit = 10
def read_limit():
return limit
def mistaken_change():
print(limit)
limit = 20
def change_limit(value):
global limit
limit = value
print(read_limit())
try:
mistaken_change()
except UnboundLocalError:
print("Имя признано локальным")
change_limit(20)
print(read_limit())
10 Имя признано локальным 20
Простое чтение read_limit не требует global. В mistaken_change присваивание заставляет трактовать limit как локальное имя, поэтому предыдущее print не успевает получить внешний предел. Исправленный пример показывает механизм, но не утверждает, что глобальное изменение является лучшим дизайном. Для большинства расчётов передать limit аргументом легче, чем помнить о скрытом изменении настройки между вызовами.
def make_counter():
value = 0
def increment():
nonlocal value
value += 1
return value
return increment
first = make_counter()
second = make_counter()
print(first(), first(), second())
1 2 1
Каждый вызов make_counter создаёт отдельное состояние. Внешняя функция уже завершилась, а внутреннее действие продолжает использовать её имя. Для такого локального счётчика nonlocal показывает намерение яснее, чем общая глобальная переменная. Тем не менее состояние усложняет повторяемость: если действие можно выразить функцией «получить старое значение, вернуть новое», этот вариант часто проще проверять.
Не используйте global как первую реакцию на любую ошибку имени. Проверьте, какие данные действительно нужны функции, и передайте их аргументами. Скрытые зависимости от переменной модуля связывают порядок запусков и затрудняют параллельное использование. Явные параметры делают необходимое окружение видимым в сигнатуре и помогают разделить независимые задачи.
Замыкание, lambda и позднее связывание
Замыкание хранит доступ к имени из окружающей функции. Оно не обязательно сохраняет копию значения в момент создания. Поэтому несколько действий, созданных в цикле, могут после завершения цикла увидеть одно его последнее значение. Ошибка особенно заметна в обработчиках кнопок: визуально создано несколько разных действий, но все обращаются к одной поздно прочитанной переменной.
def bad_callbacks():
return [lambda: number for number in range(3)]
def fixed_callbacks():
return [lambda number=number: number for number in range(3)]
print([callback() for callback in bad_callbacks()])
print([callback() for callback in fixed_callbacks()])
[2, 2, 2] [0, 1, 2]
Default во втором варианте вычисляется отдельно при создании каждой lambda и фиксирует текущее число. Для сложного действия можно написать обычную фабрику функции с аргументом. Если фиксируется изменяемый объект, default сохраняет его ссылку, а не глубокую копию. Опять сначала решите, нужно текущее состояние объекта на момент вызова или отдельный снимок.
Lambda содержит одно выражение и возвращает его результат. Она удобна для короткого ключа сортировки или небольшого преобразования рядом с местом использования. Для сложных условий, docstring и нескольких шагов обычный def читабельнее. Дайте функции имя, когда оно помогает объяснить намерение: это улучшает сообщения об ошибках и чтение вызывающего кода.
Декоратор и functools.wraps
Декоратор получает функцию и возвращает объект, который будет использоваться под её именем. Запись с @decorator применяет такое преобразование при определении. Это удобно для повторяющейся оболочки: учёта вызовов, проверки или оформления результата. Сначала проверьте простую функцию без оболочки, затем убедитесь, что декоратор не меняет её аргументы и результат неожиданным образом.
from functools import wraps
def announce_call(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Вызов:", function.__name__)
return function(*args, **kwargs)
return wrapper
@announce_call
def double(value):
"""Удваивает значение."""
return value * 2
print(double(3))
print(double.__name__)
print(double.__doc__)
print(double.__wrapped__(4))
Вызов: double 6 double Удваивает значение. 8
Без return перед вызовом исходной функции wrapper вернул бы None, даже если исходный расчёт вернул число. functools.wraps переносит полезные метаданные и добавляет связь __wrapped__, чтобы инструменты могли найти оригинал. Он не переписывает фактическое тело или параметры wrapper; оболочка всё ещё должна правильно передавать args/kwargs и результат.
Несколько декораторов применяются снизу вверх. Порядок проверки и кеширования, например, может менять поведение. Не добавляйте оболочки только ради короткой записи: они должны выражать общую задачу. Подробности метаданных и других инструментов: functools.
Рекурсия, документация и проверка договора
Рекурсия вызывает функцию из самой себя. Нужны базовый случай и уменьшение задачи, иначе вызовы не завершатся. Глубина ограничена средой, а каждый вызов использует ресурсы. Для простого последовательного накопления обычный цикл часто понятнее и не создаёт глубокую цепочку вызовов. Не увеличивайте лимит рекурсии вместо проверки алгоритма.
def factorial_recursive(n):
if type(n) is not int or n < 0:
raise ValueError("Нужно неотрицательное целое")
if n == 0:
return 1
return n * factorial_recursive(n - 1)
def factorial_loop(n):
if type(n) is not int or n < 0:
raise ValueError("Нужно неотрицательное целое")
result = 1
for value in range(2, n + 1):
result *= value
return result
for n in (0, 5):
print(factorial_recursive(n), factorial_loop(n))
1 1 120 120
Docstring описывает смысл, параметры, результат и важные исключения. Он должен дополнять имя, а не пересказывать каждую строку. Аннотация типа помогает человеку и статическому анализатору, но автоматически не проверяет аргумент в runtime. Если функция обещает только строки, запись text: str не помешает вызывающему передать число. Для внешнего ввода нужны настоящие проверки и преобразование на определённой границе.
def repeat(text: str, count: int) -> str:
return text * count
print(repeat("А", 3))
unexpected = repeat(2, 3)
print(unexpected)
print(type(unexpected).__name__)
ААА 6 int
Число здесь умножилось на число, хотя аннотация обещала строковый результат. Это воспроизводимый пример отсутствия автоматической проверки, а не рекомендуемый контракт. Статический анализатор может обнаружить несовместимый вызов до запуска, а runtime-валидация — отклонить его в самой программе. Эти проверки дополняют друг друга и решают разные задачи.
Начиная с Python 3.14 аннотации имеют отложенную модель вычисления; инструменты чтения описаны в annotationlib. Не стройте проверку данных только на наличии аннотации. Для первых функций достаточно ясного договора и тестов нормального случая, пустого ввода, границы и недопустимого типа.
Упражнение: расчёт с явной настройкой
Напишите summarize, которая получает последовательность целых, а minimum принимает только по имени. Возвратите количество и сумму значений не ниже minimum. Для пустого входа нужны ноль и ноль. Исходную последовательность не изменяйте. Отдельно отклоните bool и другие типы, если договор требует именно встроенный int. Проверьте количество и сумму как отдельные результаты, а не только напечатанную строку.
Решение упражнения
def summarize(values, /, *, minimum=0):
"""Возвращает количество подходящих целых и их сумму."""
if type(minimum) is not int:
raise TypeError("minimum должен быть int")
accepted = []
for value in values:
if type(value) is not int:
raise TypeError("Значения должны быть int")
if value >= minimum:
accepted.append(value)
return len(accepted), sum(accepted)
print(summarize([0, -2, 4, 7], minimum=0))
print(summarize([], minimum=0))
(3, 11) (0, 0)
9. Встроенные функции: полный справочник и выбор по задаче
Как найти функцию и понять её запись
Нужно отсортировать записи, посчитать элементы или открыть файл? Сначала ищите готовую операцию в стандартном наборе. Встроенные функции доступны без собственного импорта: len(items), print(message), sorted(items). Рядом находятся вызываемые типы: list(), dict(), str(). Для посетителя это один справочник, хотя функция и создание объекта решают разные задачи.
В мануале представлен полный каталог из 73 позиций официальной таблицы Python 3.15: включая frozendict, sentinel и специальную __import__. Исключения и константы, например ValueError и None, изучаются отдельно. Наличие всех позиций этой таблицы не означает каталог всех функций сторонних библиотек.
Начните с имени или задачи, затем прочитайте возвращаемое значение и ограничения. В сигнатуре косая черта отделяет позиционные параметры, одиночная звёздочка начинает именованные. Значение по умолчанию позволяет опустить аргумент. Такие обозначения объясняют, почему две внешне похожие записи вызова могут вести себя по-разному.
Какие группы пригодятся в обычной программе
Для чисел используйте abs, round, min, max, sum, pow и divmod. Но сначала определите смысл расчёта. Денежная сумма с точными десятичными правилами может требовать Decimal из стандартной библиотеки; простой вызов round не меняет способ хранения исходного float.
Для обхода данных нужны enumerate, zip, iter, next и range. Для проверки условий — all и any. Для порядка — sorted и reversed. Многие из этих операций возвращают объект для последующего обхода, поэтому добавляйте list(...) только когда действительно нужны сохранённые элементы.
Для преобразования есть int, float, str, bytes и контейнеры. Преобразование может завершиться исключением; функция не угадывает за вас формат чужого текста. Для проверки объекта применяются isinstance, issubclass, callable и hasattr. Их результат отвечает конкретному вопросу, а не подтверждает всю пригодность объекта.
Сортировать, изменить список или пройти наоборот
sorted создаёт новый список из переданного итерируемого объекта. Метод list.sort переставляет существующий список и возвращает None. reversed позволяет идти в обратном порядке, сохраняя исходные значения. Выбор определяется тем, нужно ли сохранить прежний порядок и нужен ли отдельный результат.
Обе сортировки поддерживают key. Для списка товаров ключом может быть цена, для текстов — подготовленная строка. Сортировка стабильна: элементы с одинаковым ключом сохраняют взаимный порядок. Это полезно, когда ранее выполненная сортировка или последовательность поступления имеют значение. Не сравнивайте объекты разных несопоставимых типов, рассчитывая на случайный универсальный порядок.
values = [3, 1, 2]
ordered = sorted(values)
print(values)
print(ordered)
print(list(reversed(values)))
result = values.sort()
print(values)
print(result is None)
[3, 1, 2] [1, 2, 3] [2, 1, 3] [1, 2, 3] True
Проверять длину и пустые данные
По умолчанию zip заканчивается вместе с самым коротким источником. Для связанных колонок это может незаметно потерять часть записей. Параметр strict=True, доступный с Python 3.10, превращает несоответствие длин в ошибку при обходе. Если одна колонка намеренно короче, такая проверка не подходит: сначала сформулируйте правило обработки отсутствующего значения.
all([]) даёт True, а any([]) — False. Это следствие смысла операций: среди пустого набора нет ложного элемента и нет истинного. Если бизнес-правило требует хотя бы одну запись, отдельно проверьте непустоту. У min и max для пустого итерируемого источника можно задать default; значение должно иметь понятный смысл для дальнейшего расчёта.
names = ["А", "Б"]
amounts = [10]
print(list(zip(names, amounts)))
try:
print(list(zip(names, amounts, strict=True)))
except ValueError:
print("Длины различаются")
print(all([]), any([]))
print(max([], default=None))
[('А', 10)]
Длины различаются
True False
None
Проверка типа и узнаваемое отсутствующее значение
isinstance(True, int) возвращает True: bool является подтипом int. Для поля, где разрешено только обычное целое число, это может быть слишком широкая проверка. В то же время сравнение type(value) is int намеренно исключит и пользовательские подклассы. Выбирайте проверку согласно контракту, а не привычной короткой записи.
Новый тип sentinel в Python 3.15 помогает отличить отсутствующий аргумент от явно переданного None. Сравнивайте конкретный маркер по идентичности. frozendict создаёт словарь без операций изменения; он не является подклассом dict. Его вложенные значения сохраняют собственные свойства: наличие внутри списка не делает этот список неизменяемым и мешает хешируемости всего объекта.
MISSING = sentinel("MISSING")
def describe(value=MISSING):
if value is MISSING:
return "Аргумент не передан"
if value is None:
return "Передан None"
return "Передано значение"
settings = frozendict(mode="demo", limit=10)
print(describe())
print(describe(None))
print(settings["limit"])
print(settings == frozendict(limit=10, mode="demo"))
try:
settings["limit"] = 20
except TypeError:
print("Словарь не изменяется")
Аргумент не передан Передан None 10 True Словарь не изменяется
Результат проверки и пределы возможностей
callable сообщает, что объект выглядит вызываемым; конкретный вызов всё равно может завершиться ошибкой аргументов или внутреннего расчёта. hasattr проверяет доступ к атрибуту и может вызвать логику свойства. Для подробного исследования пригодятся help, dir, vars и модуль inspect. Полученный список имён не заменяет описание публичного API.
eval, exec и compile работают с кодом. Не используйте их как удобное преобразование пользовательской строки в число или список. Для известных форматов существуют int, JSON и другие целевые парсеры. open рассматривайте вместе с контекстным менеджером и кодировкой, а input — вместе с обработкой полученной строки.
Практический маршрут справочника короткий: выбрать операцию, определить форму данных, проверить результат на пустом и неправильном вводе, затем встроить в функцию своей программы. Если знакомая запись не работает, сверяйте минимальную версию и сигнатуру. Не заменяйте такую проверку установкой похожего пакета: встроенная функция уже принадлежит интерпретатору.
Попробуйте самостоятельно. Для двух колонок из CSV нужно сохранить каждую пару и остановить обработку, если длины различаются. Какую запись выбрать?
Посмотреть решение
Используйте zip(first, second, strict=True) и обработайте ValueError вокруг фактического обхода. Если требуется сохранить результат, создайте list внутри этого блока. Затем отдельно решите, допустимы ли обе пустые колонки: all не проверяет наличие данных.
Все 73 встроенные функции и вызываемые типы
Откройте нужное имя: назначение, нюанс, пример использования и точная ссылка на документацию. Интерактивный breakpoint обозначен отдельно.
absФункция · Числа
Возвращает модуль числа.
Нюанс. Для комплексного числа результатом будет его величина, а не отдельно изменённые части.
abs(number, /) — упрощённо; остальные варианты в документации
Получить расстояние от нуля или величину отклонения независимо от его знака. Для комплексного числа результатом будет его модуль.
expected = 18
actual = 13
print(abs(actual - expected))
Ожидаемый вывод:
5Официальная документация: abs
aiterФункция · Асинхронность
Получает асинхронный итератор объекта.
Нюанс. Варианта с вызываемым объектом и стоп-значением, как у iter(), здесь нет.
aiter(async_iterable, /) — упрощённо; остальные варианты в документации
Начать ручной обход асинхронного источника. Полученный итератор используют с anext/await; обычный iter для него не подходит.
import asyncio
async def source():
yield 7
async def main():
iterator = aiter(source())
print(iterator is aiter(iterator))
print(await anext(iterator))
await iterator.aclose()
asyncio.run(main())
Ожидаемый вывод:
True 7Официальная документация: aiter
allФункция · Проверки
Проверяет, истинны ли все элементы перебираемого объекта.
Нюанс. Для пустого объекта возвращает True.
all(iterable, /) — упрощённо; остальные варианты в документации
Проверить, что все условия выполнены. Для пустого источника результат True; если нужны ещё и данные, отдельно проверяйте непустоту.
quantities = [2, 0, 4]
print(all(value >= 0 for value in quantities))
print(all([]))
Ожидаемый вывод:
True TrueОфициальная документация: all
anextФункция · Асинхронность
Получает ожидание следующего элемента асинхронного итератора.
Нюанс. Результат нужно ожидать через await; без default конец итерации вызывает StopAsyncIteration.
anext(async_iterator[, default], /) — упрощённо; остальные варианты в документации
Получить один элемент асинхронного итератора через await. Значение default позволяет обработать завершение без StopAsyncIteration.
import asyncio
async def source():
yield 'готово'
async def main():
iterator = aiter(source())
print(await anext(iterator))
print(await anext(iterator, 'конец'))
asyncio.run(main())
Ожидаемый вывод:
готово конецОфициальная документация: anext
anyФункция · Проверки
Проверяет, есть ли хотя бы один истинный элемент.
Нюанс. Для пустого объекта возвращает False.
any(iterable, /) — упрощённо; остальные варианты в документации
Узнать, найдена ли хотя бы одна подходящая запись. Пустой источник даёт False; проверка останавливается на первом истинном элементе.
statuses = ['new', 'error', 'done']
print(any(status == 'error' for status in statuses))
print(any([]))
Ожидаемый вывод:
True FalseОфициальная документация: any
asciiФункция · Представление и форматирование
Создаёт представление объекта с экранированными не-ASCII символами.
Нюанс. Это представление для просмотра, а не преобразование текстового файла в ASCII.
ascii(object, /) — упрощённо; остальные варианты в документации
Показать отладочное представление с экранированными не-ASCII символами. Это представление, а не кодировка исходного файла.
word = 'ёж'
escaped = ascii(word)
print(escaped)
Ожидаемый вывод:
'\u0451\u0436'Официальная документация: ascii
binФункция · Числа
Записывает целое число двоичной строкой.
Нюанс. В результат входит префикс 0b; для другого оформления используйте format().
bin(integer, /) — упрощённо; остальные варианты в документации
Показать целое число в двоичном виде, например при разборе битовых флагов. Для заданной ширины и без префикса используйте форматирование.
flags = 10
print(bin(flags))
print(format(flags, '08b'))
Ожидаемый вывод:
0b1010 00001010Официальная документация: bin
boolТип / конструктор · Типы и преобразования
Преобразует объект в True или False по правилам истинности.
Нюанс. bool является подклассом int, поэтому проверки на int включают булевы значения.
bool(object=False, /) — упрощённо; остальные варианты в документации
Явно получить True/False по правилам истинности. Непустая строка «False» тоже истинна: это не разбор текстовой настройки.
values = [[], [0], '', 'False']
for value in values:
print(bool(value))
Ожидаемый вывод:
False True False TrueОфициальная документация: bool
breakpointФункция · Отладка
Передаёт управление отладчику в месте вызова.
Нюанс. Поведение зависит от sys.breakpointhook и переменной PYTHONBREAKPOINT.
breakpoint(*args, **kwargs) — упрощённо; остальные варианты в документации
Интерактивно остановиться и исследовать значения в отладчике. Пример требует человека: он исключён из автоматической проверки, приглашения отладчика не объявлены стабильным stdout.
Интерактивный фрагмент. Требует участия человека; автоматически не запускался. В архиве он отделён от проверенных run-примеров.
subtotal = 120 + 80
discount = 20
breakpoint()
print(subtotal - discount)
Официальная документация: breakpointbytearrayТип / конструктор · Байты и буферы
Создаёт изменяемую последовательность байтов.
Нюанс. Целый аргумент задаёт длину массива нулевых байтов, а не код одного символа.
bytearray(source=...); bytearray(text, encoding, errors="strict") — упрощённо; остальные варианты в документации
Создать изменяемый буфер байтов для обработки двоичных данных. Значения одного элемента должны находиться в диапазоне 0–255.
buffer = bytearray(b'cat')
buffer[0] = ord('b')
print(buffer.decode('ascii'))
print(bytes(buffer))
Ожидаемый вывод:
bat b'bat'Официальная документация: bytearray
bytesТип / конструктор · Байты и буферы
Создаёт неизменяемую последовательность байтов.
Нюанс. Для преобразования строки необходимо указать кодировку.
bytes(source=...); bytes(text, encoding, errors="strict") — упрощённо; остальные варианты в документации
Получить неизменяемые байты или закодировать текст с явной кодировкой. bytes(3) создаёт три нулевых байта, а не текст «3».
text = 'Ё'
data = bytes(text, 'utf-8')
print(list(data))
print(data.decode('utf-8'))
Ожидаемый вывод:
[208, 129] ЁОфициальная документация: bytes
callableФункция · Проверки
Проверяет, допускает ли объект вызов.
Нюанс. True не гарантирует, что конкретный вызов с выбранными аргументами завершится успешно.
callable(object, /) — упрощённо; остальные варианты в документации
Проверить, можно ли вызвать объект как функцию. Положительный результат не гарантирует, что конкретные аргументы подходят этому вызову.
handler = lambda value: value * 2
print(callable(handler))
print(callable(42))
print(handler(5))
Ожидаемый вывод:
True False 10Официальная документация: callable
chrФункция · Текст и Unicode
Возвращает символ Unicode по его числовому коду.
Нюанс. Допустимы коды от 0 до 0x10FFFF; за пределами возникает ValueError.
chr(codepoint, /) — упрощённо; остальные варианты в документации
Преобразовать числовой код Unicode в символ, например при разборе собственного текстового формата. Это операция над кодовой точкой, не над произвольным байтом UTF-8.
codepoint = 0x0410
letter = chr(codepoint)
print(letter)
print(ord(letter) == codepoint)
Ожидаемый вывод:
А TrueОфициальная документация: chr
classmethodДекоратор / тип · Классы и атрибуты
Создаёт метод, получающий класс первым аргументом.
Нюанс. При вызове через подкласс первым аргументом будет этот подкласс.
classmethod(function); обычно @classmethod — упрощённо; остальные варианты в документации
Создать альтернативный конструктор, который получает класс как cls и сохраняет нужный класс при наследовании.
class Quantity:
def __init__(self, value):
self.value = value
@classmethod
def from_text(cls, text):
return cls(int(text))
class SpecialQuantity(Quantity):
pass
value = SpecialQuantity.from_text('12')
print(type(value).__name__)
print(value.value)
Ожидаемый вывод:
SpecialQuantity 12Официальная документация: classmethod
compileФункция · Выполнение кода
Компилирует исходный текст в объект кода или AST.
Нюанс. Режимы exec, eval и single предназначены для разных форм исходного текста.
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) — упрощённо; остальные варианты в документации
Подготовить собственный литерал к повторному выполнению или проверить его синтаксис. compile/eval/exec не образуют песочницу для чужого кода.
code = compile('3 * 7', '<owned-demo>', 'eval')
print(type(code).__name__)
print(eval(code))
Ожидаемый вывод:
code 21Официальная документация: compile
complexТип / конструктор · Типы и преобразования
Создаёт комплексное число из числа, строки или пары частей.
Нюанс. Передача комплексных значений в отдельные real и imag устарела с Python 3.14.
complex(real=0, imag=0); также complex(text) — упрощённо; остальные варианты в документации
Представить число с действительной и мнимой частью для соответствующего расчёта. Строковый и числовой способы создания имеют разные правила аргументов.
value = complex(2, 3)
print(value)
print(value * value)
print(value.real, value.imag)
Ожидаемый вывод:
(2+3j) (-5+12j) 2.0 3.0Официальная документация: complex
delattrФункция · Классы и атрибуты
Удаляет атрибут объекта по строковому имени.
Нюанс. Объект должен разрешать удаление; операция эквивалентна del для этого атрибута.
delattr(object, name, /) — упрощённо; остальные варианты в документации
Удалить атрибут по известному строковому имени. Используйте только допустимые имена своего объекта; дескриптор может задавать собственные правила удаления.
class Draft:
pass
item = Draft()
item.note = 'черновик'
delattr(item, 'note')
print(hasattr(item, 'note'))
Ожидаемый вывод:
FalseОфициальная документация: delattr
dictТип / конструктор · Контейнеры
Создаёт словарь соответствий между ключами и значениями.
Нюанс. Ключи должны быть хешируемыми; порядок добавления сохраняется.
dict(); dict(mapping); dict(pairs); dict(**kwargs) — упрощённо; остальные варианты в документации
Построить соответствие ключей значениям. Ключи должны быть хешируемыми; повторное имя заменяет значение, а порядок вставки сохраняется.
pairs = [('a', 1), ('b', 3), ('a', 2)]
record = dict(pairs)
print(sorted(record.items()))
print(record['a'])
Ожидаемый вывод:
[('a', 2), ('b', 3)]
2
Официальная документация: dictdirФункция · Отладка
Выводит имена в текущей области или доступные атрибуты объекта.
Нюанс. Список удобен для исследования, но не обязан быть исчерпывающим.
dir(); dir(object, /) — упрощённо; остальные варианты в документации
Узнать доступные имена при исследовании объекта. Результат зависит от объекта и не заменяет полный контракт API или проверку наличия конкретного атрибута.
names = dir([])
interesting = [name for name in names if name in {'append', 'clear'}]
print(sorted(interesting))
Ожидаемый вывод:
['append', 'clear']Официальная документация: dir
divmodФункция · Числа
Возвращает пару из частного с делением вниз и остатка.
Нюанс. Для целых остаток следует знаку делителя, что важно при отрицательных числах.
divmod(a, b, /) — упрощённо; остальные варианты в документации
Одновременно получить целое частное с делением вниз и остаток, например разбить целое число секунд на минуты и остаток секунд.
seconds = 125
minutes, remainder = divmod(seconds, 60)
print(minutes, remainder)
Ожидаемый вывод:
2 5Официальная документация: divmod
enumerateТип / конструктор · Итерация
Перебирает элементы вместе со счётчиком.
Нюанс. Счётчик по умолчанию начинается с нуля и меняется параметром start.
enumerate(iterable, start=0) — упрощённо; остальные варианты в документации
Перебирать строки или записи вместе с номером, не управляя отдельным счётчиком вручную. start задаёт первый номер.
names = ['Кабель', 'Клемма']
for number, name in enumerate(names, start=1):
print(number, name)
Ожидаемый вывод:
1 Кабель 2 КлеммаОфициальная документация: enumerate
evalФункция · Выполнение кода
Вычисляет выражение Python и возвращает его результат.
Нюанс. Непроверенный пользовательский текст способен выполнить произвольный код.
eval(source, /, globals=None, locals=None) — упрощённо; остальные варианты в документации
Вычислить известное выражение Python. В примере используется только наш литерал; удаление builtins из globals не делает eval безопасной песочницей для внешних выражений.
expression = 'price * quantity'
namespace = {'price': 12, 'quantity': 3}
print(eval(expression, {'__builtins__': {}}, namespace))
Ожидаемый вывод:
36Официальная документация: eval
execФункция · Выполнение кода
Выполняет набор инструкций Python из текста или объекта кода.
Нюанс. Непроверенный пользовательский текст способен выполнить произвольный код.
exec(source, /, globals=None, locals=None, *, closure=None) — упрощённо; остальные варианты в документации
Выполнить собственные инструкции в явно выбранном пространстве имён. Не применять к чужому тексту как к формату данных: это исполнение кода, а не безопасный разбор.
namespace = {'value': 4}
exec('result = value * 2', namespace)
print(namespace['result'])
Ожидаемый вывод:
8Официальная документация: exec
filterТип / конструктор · Итерация
Лениво оставляет элементы, для которых условие истинно.
Нюанс. При function=None отбрасываются все ложные элементы, включая нули и пустые строки.
filter(function, iterable, /) — упрощённо; остальные варианты в документации
Лениво оставить элементы, прошедшие проверку. Результат — одноразовый итератор; для повторного использования материализуйте нужный небольшой список.
source = [-2, 0, 3, 5]
selected = filter(lambda value: value > 0, source)
print(list(selected))
print(list(selected))
Ожидаемый вывод:
[3, 5] []Официальная документация: filter
floatТип / конструктор · Типы и преобразования
Преобразует число или числовую строку в число с плавающей точкой.
Нюанс. Строка должна соответствовать синтаксису Python; десятичная запятая не принимается.
float(number=0, /); также float(text, /) — упрощённо; остальные варианты в документации
Прочитать число с плавающей точкой из десятичной строки. Для точных денежных правил выбирайте подходящее представление; float не гарантирует десятичную точность.
value = float('12.5')
print(value)
print(value.is_integer())
Ожидаемый вывод:
12.5 FalseОфициальная документация: float
formatФункция · Представление и форматирование
Форматирует одно значение по указанной спецификации.
Нюанс. Спецификация зависит от типа объекта и его метода __format__().
format(value, format_spec="", /) — упрощённо; остальные варианты в документации
Получить форматированную строку одного значения: сумму с двумя знаками, номер нужной ширины или иной формат, поддержанный типом.
amount = 12.5
print(format(amount, '.2f'))
print(format(42, '04d'))
Ожидаемый вывод:
12.50 0042Официальная документация: format
frozendictТип / конструктор · Контейнеры
Создаёт неизменяемый словарь с интерфейсом чтения dict.
Нюанс. Появился в 3.15; хешируется только тогда, когда хешируются все ключи и значения.
frozendict(); frozendict(mapping_or_pairs); frozendict(**kwargs) — упрощённо; остальные варианты в документации
Начиная с Python 3.15 создать неизменяемое отображение для настроек или передаваемого снимка. Оно не замораживает автоматически изменяемые вложенные значения.
settings = frozendict({'limit': 3, 'mode': 'demo'})
print(sorted(settings.items()))
try:
settings['limit'] = 4
except TypeError:
print('Изменение запрещено')
Ожидаемый вывод:
[('limit', 3), ('mode', 'demo')]
Изменение запрещено
Официальная документация: frozendictfrozensetТип / конструктор · Контейнеры
Создаёт неизменяемое множество уникальных элементов.
Нюанс. Элементы должны быть хешируемыми; само множество также можно использовать как ключ.
frozenset(iterable=(), /) — упрощённо; остальные варианты в документации
Сохранить неизменяемое множество уникальных хешируемых элементов, в том числе как ключ словаря или элемент другого множества. Порядок элементов не задан.
selected = frozenset([3, 1, 3])
print(sorted(selected))
print(len({selected}))
Ожидаемый вывод:
[1, 3] 1Официальная документация: frozenset
getattrФункция · Классы и атрибуты
Читает атрибут объекта по строковому имени.
Нюанс. Если атрибут отсутствует, возвращает default или вызывает AttributeError.
getattr(object, name[, default], /) — упрощённо; остальные варианты в документации
Прочитать известный атрибут по имени, например при выборе разрешённого параметра. Значение default используется, когда атрибут отсутствует; property при чтении может выполнять код.
class Settings:
limit = 10
settings = Settings()
print(getattr(settings, 'limit'))
print(getattr(settings, 'missing', 0))
Ожидаемый вывод:
10 0Официальная документация: getattr
globalsФункция · Области видимости
Возвращает словарь глобальных имён текущего модуля.
Нюанс. Внутри функции это пространство модуля определения функции, а не места вызова.
globals() — упрощённо; остальные варианты в документации
Получить отображение глобальных имён текущего модуля при диагностике собственного кода. Не выводите весь словарь: в нём могут быть данные, не предназначенные для публикации.
DEMO_COUNT = 7
namespace = globals()
print(namespace['DEMO_COUNT'])
print('DEMO_COUNT' in namespace)
Ожидаемый вывод:
7 TrueОфициальная документация: globals
hasattrФункция · Классы и атрибуты
Проверяет доступность атрибута по имени.
Нюанс. Проверка вызывает getattr(), поэтому может запустить property или другой дескриптор.
hasattr(object, name, /) — упрощённо; остальные варианты в документации
Проверить доступность конкретного атрибута. Проверка выполняет обращение к нему: дескриптор может запуститься, а исключение кроме AttributeError может выйти наружу.
class Item:
name = 'demo'
item = Item()
print(hasattr(item, 'name'))
print(hasattr(item, 'price'))
Ожидаемый вывод:
True FalseОфициальная документация: hasattr
hashФункция · Проверки
Возвращает хеш хешируемого объекта.
Нюанс. Разные объекты могут иметь один хеш; совпадение хешей не доказывает равенство.
hash(object, /) — упрощённо; остальные варианты в документации
Получить хеш хешируемого ключа для понимания работы dict/set. Хеш не является уникальным идентификатором; значения для ряда типов могут меняться между процессами.
key = (7, 'web')
value = hash(key)
print(isinstance(value, int))
print(value == hash((7, 'web')))
Ожидаемый вывод:
True TrueОфициальная документация: hash
helpФункция · Отладка
Показывает справку по объекту или запускает интерактивную справочную систему.
Нюанс. Без аргумента запускает диалог в консоли, а не возвращает готовый текст документации.
help(); help(object) — упрощённо; остальные варианты в документации
Посмотреть справку по функции или типу. Без аргумента включается интерактивная система; здесь справка по len реально вызывается и перехватывается для устойчивой проверки.
import io
from contextlib import redirect_stdout
buffer = io.StringIO()
with redirect_stdout(buffer):
help(len)
print('len' in buffer.getvalue())
Ожидаемый вывод:
TrueОфициальная документация: help
hexФункция · Числа
Записывает целое число шестнадцатеричной строкой.
Нюанс. В результат входят префикс 0x и строчные буквы.
hex(integer, /) — упрощённо; остальные варианты в документации
Представить целое число шестнадцатеричной строкой, например для чтения кодов. Для нулевого заполнения и отсутствия префикса используйте format.
value = 255
print(hex(value))
print(format(value, '04x'))
Ожидаемый вывод:
0xff 00ffОфициальная документация: hex
idФункция · Проверки
Возвращает идентификатор конкретного объекта.
Нюанс. После завершения жизни объекта тот же идентификатор может получить другой объект.
id(object, /) — упрощённо; остальные варианты в документации
Проверить, относятся ли ссылки к одному живому объекту. Не сохраняйте числовой id как постоянный ключ: после завершения жизни объекта значение может использоваться повторно.
record = []
alias = record
other = []
print(id(record) == id(alias))
print(id(record) != id(other))
Ожидаемый вывод:
True TrueОфициальная документация: id
inputФункция · Ввод и вывод
Читает строку из стандартного ввода.
Нюанс. Результат всегда строка; конец ввода вызывает EOFError.
input(prompt="", /) — упрощённо; остальные варианты в документации
Прочитать одну строку из стандартного ввода. Результат — str без завершающего перевода строки; числа и границы проверяют отдельно. Для этого примера stdin задан заранее.
Ввод для показанного результата:
Кабель
raw = input()
name = raw.strip()
print(name)
print(len(name))
Ожидаемый вывод:
Кабель 6Официальная документация: input
intТип / конструктор · Типы и преобразования
Преобразует число или строку в целое число.
Нюанс. При преобразовании float дробная часть отбрасывается к нулю, а не округляется вниз.
int(number=0, /); int(text, base=10) — упрощённо; остальные варианты в документации
Получить целое из строки или числа, либо разобрать запись в заданной системе счисления. Преобразование float отбрасывает дробную часть к нулю, а не округляет вниз.
quantity = int('12')
print(quantity + 3)
print(int('ff', 16))
print(int(-3.9))
Ожидаемый вывод:
15 255 -3Официальная документация: int
isinstanceФункция · Проверки
Проверяет принадлежность объекта к типу или его подклассам.
Нюанс. Проверка учитывает наследование; для строгого сравнения конкретного типа нужен type().
isinstance(object, classinfo, /) — упрощённо; остальные варианты в документации
Проверить тип с учётом наследования и принять несколько типов. bool наследуется от int: если логические значения запрещены, одной такой проверки целого недостаточно.
value = True
print(isinstance(value, int))
print(type(value) is int)
print(isinstance('report', (str, bytes)))
Ожидаемый вывод:
True False TrueОфициальная документация: isinstance
issubclassФункция · Проверки
Проверяет, наследуется ли один класс от другого.
Нюанс. Класс считается подклассом самого себя.
issubclass(class, classinfo, /) — упрощённо; остальные варианты в документации
Проверить отношение между классами при выборе реализации или регистрации обработчика. Аргументом должен быть класс, а не его экземпляр.
class Report:
pass
class CsvReport(Report):
pass
print(issubclass(CsvReport, Report))
print(issubclass(Report, CsvReport))
Ожидаемый вывод:
True FalseОфициальная документация: issubclass
iterФункция · Итерация
Получает обычный итератор или повторно вызывает функцию до стоп-значения.
Нюанс. В варианте с двумя аргументами остановка определяется равенством результата стоп-значению.
iter(iterable, /); iter(callable, sentinel, /) — упрощённо; остальные варианты в документации
Получить обычный итератор либо вызывать функцию до стоп-значения. Итератор расходуется при обходе; второй вариант удобен для чтения последовательных блоков.
source = iter([1, 2, 0, 3])
blocks = iter(lambda: next(source), 0)
print(list(blocks))
print(next(source))
Ожидаемый вывод:
[1, 2] 3Официальная документация: iter
lenФункция · Контейнеры
Возвращает количество элементов объекта.
Нюанс. Для строки считает кодовые точки Unicode, которые не всегда совпадают с видимыми символами.
len(object, /) — упрощённо; остальные варианты в документации
Узнать число элементов контейнера или кодовых точек строки. Длина Unicode-строки не обязательно равна числу видимых символов или размеру UTF-8 в байтах.
text = 'Ёж'
print(len(text))
print(len(text.encode('utf-8')))
Ожидаемый вывод:
2 4Официальная документация: len
listТип / конструктор · Контейнеры
Создаёт изменяемый список из перебираемого объекта.
Нюанс. Преобразование другого контейнера не делает глубокие копии вложенных элементов.
list(iterable=(), /) — упрощённо; остальные варианты в документации
Материализовать небольшой источник или создать изменяемую последовательность. Копирование внешнего контейнера не делает глубокой копии вложенных изменяемых объектов.
source = range(3)
values = list(source)
values.append(9)
print(values)
Ожидаемый вывод:
[0, 1, 2, 9]Официальная документация: list
localsФункция · Области видимости
Возвращает отображение локальных имён и их значений.
Нюанс. В функциях возвращает новый снимок; изменение этого словаря не переназначает локальные переменные.
locals() — упрощённо; остальные варианты в документации
Посмотреть локальные значения собственной функции при исследовании. Не используйте изменение возвращённого отображения как переносимый способ присвоить локальную переменную.
def describe(name, count):
values = locals()
return values['name'], values['count']
print(describe('demo', 3))
Ожидаемый вывод:
('demo', 3)
Официальная документация: localsmapТип / конструктор · Итерация
Лениво применяет функцию к элементам одного или нескольких перебираемых объектов.
Нюанс. По умолчанию останавливается на самом коротком; strict=True проверяет равенство длин.
map(function, iterable, /, *iterables, strict=False) — упрощённо; остальные варианты в документации
Лениво преобразовать записи функцией. Итератор одноразовый; при нескольких источниках обычный режим останавливается на коротком. strict доступен с Python 3.14.
converted = map(int, ['1', '2', '3'])
values = list(converted)
print(values)
print(list(converted))
Ожидаемый вывод:
[1, 2, 3] []Официальная документация: map
maxФункция · Числа и порядок
Возвращает наибольший элемент по самому значению или ключу сравнения.
Нюанс. Для пустого iterable нужен default, иначе возникает ValueError.
max(iterable, /, *, key=None[, default]); max(a, b, ...) — упрощённо; остальные варианты в документации
Выбрать запись с наибольшим ключом сравнения. default применим к пустому iterable; при равных ключах возвращается первый встретившийся элемент.
records = [{'name': 'A', 'count': 2}, {'name': 'B', 'count': 5}]
best = max(records, key=lambda row: row['count'])
print(best['name'])
print(max([], default='нет данных'))
Ожидаемый вывод:
B нет данныхОфициальная документация: max
memoryviewТип / конструктор · Байты и буферы
Предоставляет доступ к буферу без обязательного копирования данных.
Нюанс. Если исходный буфер изменяемый, изменения через представление затрагивают исходные данные.
memoryview(object) — упрощённо; остальные варианты в документации
Работать с поддерживающим буфер объектом без обязательного копирования байтов. Изменение доступного для записи view меняет исходный буфер; время жизни view важно для изменения размера буфера.
buffer = bytearray(b'abc')
with memoryview(buffer) as view:
view[1] = ord('Z')
print(view.tolist())
print(buffer.decode('ascii'))
Ожидаемый вывод:
[97, 90, 99] aZcОфициальная документация: memoryview
minФункция · Числа и порядок
Возвращает наименьший элемент по самому значению или ключу сравнения.
Нюанс. Для пустого iterable нужен default, иначе возникает ValueError.
min(iterable, /, *, key=None[, default]); min(a, b, ...) — упрощённо; остальные варианты в документации
Выбрать минимальное значение или запись по ключу, например самое дешёвое предложение. Для пустого источника явно решите, нужен default или ошибка.
prices = [12, 7, 9]
print(min(prices))
print(min([], default='нет цен'))
Ожидаемый вывод:
7 нет ценОфициальная документация: min
nextФункция · Итерация
Получает следующий элемент обычного итератора.
Нюанс. Без default конец итерации вызывает StopIteration.
next(iterator[, default], /) — упрощённо; остальные варианты в документации
Получить один элемент без полного обхода. Без default исчерпанный итератор вызывает StopIteration; с default позволяет явно обработать пустой остаток.
values = iter([10])
print(next(values))
print(next(values, 'конец'))
Ожидаемый вывод:
10 конецОфициальная документация: next
objectТип / конструктор · Классы и атрибуты
Создаёт базовый объект, от класса которого наследуются остальные классы.
Нюанс. У экземпляра object нет __dict__, поэтому произвольные атрибуты ему не назначить.
object() — упрощённо; остальные варианты в документации
Создать отдельный маркер отсутствия значения в переносимом коде. Сравнивайте его через is; в Python 3.15 для именованного маркера также существует sentinel.
MISSING = object()
record = {'value': None}
print(record.get('missing', MISSING) is MISSING)
print(record.get('value', MISSING) is MISSING)
Ожидаемый вывод:
True FalseОфициальная документация: object
octФункция · Числа
Записывает целое число восьмеричной строкой.
Нюанс. В результат входит префикс 0o.
oct(integer, /) — упрощённо; остальные варианты в документации
Представить целое в восьмеричном виде, например при чтении числовых масок. Префикс 0o отличает такую запись от десятичной.
value = 64
print(oct(value))
print(format(value, 'o'))
Ожидаемый вывод:
0o100 100Официальная документация: oct
openФункция · Ввод и вывод
Открывает файл и возвращает объект для чтения или записи.
Нюанс. Режим w обнуляет существующий файл; ресурс удобно закрывать конструкцией with.
open(file, mode="r", ..., encoding=None, errors=None, newline=None) — упрощённо; остальные варианты в документации
Открыть файл с понятным режимом и кодировкой; with закрывает ресурс. Режим w перезаписывает содержимое. Пример работает только в собственной временной папке.
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
path = Path(folder) / 'note.txt'
with open(path, 'w', encoding='utf-8') as stream:
stream.write('Привет\n')
with open(path, encoding='utf-8') as stream:
print(stream.read(), end='')
Ожидаемый вывод:
ПриветОфициальная документация: open
ordФункция · Текст и Unicode
Возвращает код одного символа Unicode или значение одного байта.
Нюанс. Строка, bytes или bytearray должны иметь длину ровно один.
ord(character, /) — упрощённо; остальные варианты в документации
Получить код одной кодовой точки Unicode или одного байта. Для строки из нескольких символов ord не подходит: сначала нужен явный выбор одного элемента.
letter = 'А'
print(ord(letter))
print(chr(ord(letter)) == letter)
Ожидаемый вывод:
1040 TrueОфициальная документация: ord
powФункция · Числа
Возводит число в степень, при необходимости вычисляя результат по модулю.
Нюанс. При целых аргументах отрицательная степень с mod означает поиск модульного обратного числа.
pow(base, exp, mod=None) — упрощённо; остальные варианты в документации
Вычислить степень; для целых со значением mod — результат по модулю без большого промежуточного числа. Отрицательная степень в модульной форме может находить обратный элемент.
base = 2
print(pow(base, 10))
print(pow(3, -1, 11))
Ожидаемый вывод:
1024 4Официальная документация: pow
printФункция · Ввод и вывод
Выводит текстовые представления объектов в текстовый поток.
Нюанс. Параметры sep, end, file и flush передаются только по имени.
print(*objects, sep=" ", end="\n", file=None, flush=False) — упрощённо; остальные варианты в документации
Вывести значения человеку или в выбранный текстовый поток. sep разделяет аргументы, end завершает запись; это не метод сериализации произвольных структур.
columns = ['name', 'quantity']
print(*columns, sep=';')
print('готово', end='!\n')
Ожидаемый вывод:
name;quantity готово!Официальная документация: print
propertyДекоратор / тип · Классы и атрибуты
Создаёт управляемый атрибут с функциями чтения, записи и удаления.
Нюанс. Без setter присваивание этому свойству запрещено.
property(fget=None, fset=None, fdel=None, doc=None); обычно @property — упрощённо; остальные варианты в документации
Предоставить вычисляемый или управляемый атрибут объекта. Getter выполняется при обращении; без setter обычное присваивание этому свойству запрещено.
class Invoice:
def __init__(self):
self._cents = 1250
@property
def amount(self):
return self._cents / 100
item = Invoice()
print(item.amount)
try:
item.amount = 99
except AttributeError:
print('Только чтение')
Ожидаемый вывод:
12.5 Только чтениеОфициальная документация: property
rangeТип / конструктор · Контейнеры
Создаёт компактную неизменяемую последовательность целых чисел.
Нюанс. Правая граница stop не входит в последовательность, а step не может быть нулём.
range(stop, /); range(start, stop, step=1, /) — упрощённо; остальные варианты в документации
Описать последовательность целых без хранения списка всех значений. Граница stop не включается, шаг не может быть нулём.
values = range(2, 10, 3)
print(list(values))
print(len(values))
Ожидаемый вывод:
[2, 5, 8] 3Официальная документация: range
reprФункция · Представление и форматирование
Возвращает представление объекта для исследования и отладки.
Нюанс. Не каждое представление является выражением, пригодным для обратного восстановления объекта.
repr(object, /) — упрощённо; остальные варианты в документации
Получить представление для отладки, позволяющее увидеть кавычки и управляющие символы. Не рассчитывайте, что repr любого объекта можно безопасно восстановить через eval.
text = 'line\nend'
representation = repr(text)
print(representation)
Ожидаемый вывод:
'line\nend'Официальная документация: repr
reversedТип / конструктор · Итерация
Создаёт итератор по элементам в обратном порядке.
Нюанс. Не сортирует значения и требует поддержки обратной итерации либо протокола последовательности.
reversed(sequence, /) — упрощённо; остальные варианты в документации
Обойти подходящую последовательность в обратном направлении без изменения исходного списка. Произвольный одноразовый итератор не всегда поддерживает reversed.
values = [1, 2, 3]
print(list(reversed(values)))
print(values)
Ожидаемый вывод:
[3, 2, 1] [1, 2, 3]Официальная документация: reversed
roundФункция · Числа
Округляет число до выбранной десятичной позиции.
Нюанс. Равноудалённые значения округляет к чётному; двоичная точность float влияет на результат.
round(number, ndigits=None) — упрощённо; остальные варианты в документации
Округлить по правилам типа. Для встроенных чисел половины обычно идут к чётному; двоичное представление float может отдельно влиять на десятичный результат.
values = [2.5, 3.5]
print([round(value) for value in values])
print(round(2.675, 2))
Ожидаемый вывод:
[2, 4] 2.67Официальная документация: round
setТип / конструктор · Контейнеры
Создаёт изменяемое множество уникальных хешируемых элементов.
Нюанс. Порядок обхода не гарантируется, а литерал {} создаёт словарь.
set(iterable=(), /) — упрощённо; остальные варианты в документации
Убрать повторы и выполнять проверки принадлежности или операции множеств. Элементы должны быть хешируемыми, порядок для вывода нужно задавать отдельно.
source = [3, 1, 3, 2]
unique = set(source)
print(sorted(unique))
print(2 in unique)
Ожидаемый вывод:
[1, 2, 3] TrueОфициальная документация: set
setattrФункция · Классы и атрибуты
Назначает атрибуту объекта значение по строковому имени.
Нюанс. Объект должен разрешать назначение; имя не обязательно является допустимым идентификатором Python.
setattr(object, name, value, /) — упрощённо; остальные варианты в документации
Назначить атрибут по известному строковому имени, например при заполнении своего объекта. Для внешних данных используйте разрешённый список имён, а не произвольное назначение.
class Draft:
pass
item = Draft()
setattr(item, 'title', 'Отчёт')
print(item.title)
Ожидаемый вывод:
ОтчётОфициальная документация: setattr
sentinelТип / конструктор · Типы и преобразования
Создаёт уникальный именованный маркер для отсутствующего или особого значения.
Нюанс. Появился в 3.15; маркеры истинны и для проверки их идентичности используют is.
sentinel(name, /, *, repr=None) — упрощённо; остальные варианты в документации
Начиная с Python 3.15 создать уникальный именованный маркер, отличающий отсутствие аргумента от None. Сравнивайте его через is; сам маркер истинный.
MISSING = sentinel('MISSING')
def describe(value=MISSING):
if value is MISSING:
return 'Не передано'
return repr(value)
print(describe())
print(describe(None))
print(bool(MISSING))
Ожидаемый вывод:
Не передано None TrueОфициальная документация: sentinel
sliceТип / конструктор · Индексы и срезы
Создаёт объект, описывающий границы и шаг среза.
Нюанс. Сам объект ничего не вырезает; его передают индексируемому объекту.
slice(stop); slice(start, stop, step=None) — упрощённо; остальные варианты в документации
Сохранить правила среза в отдельном объекте и применить их к последовательности. indices помогает получить нормализованные границы для конкретной длины.
values = list(range(6))
selection = slice(1, 6, 2)
print(values[selection])
print(selection.indices(len(values)))
Ожидаемый вывод:
[1, 3, 5] (1, 6, 2)Официальная документация: slice
sortedФункция · Числа и порядок
Возвращает новый список элементов в отсортированном порядке.
Нюанс. Исходный контейнер сохраняется; элементы с одинаковым ключом сохраняют взаимный порядок.
sorted(iterable, /, *, key=None, reverse=False) — упрощённо; остальные варианты в документации
Получить новый отсортированный список без изменения источника. key задаёт смысл сравнения; сортировка стабильна, поэтому элементы с равными ключами сохраняют прежний порядок.
names = ['B', 'a', 'A']
print(sorted(names, key=str.casefold))
print(names)
Ожидаемый вывод:
['a', 'A', 'B'] ['B', 'a', 'A']Официальная документация: sorted
staticmethodДекоратор / тип · Классы и атрибуты
Создаёт метод без автоматически передаваемого экземпляра или класса.
Нюанс. При вызове Python не добавляет self или cls.
staticmethod(function); обычно @staticmethod — упрощённо; остальные варианты в документации
Связать вспомогательную функцию с классом, не передавая автоматически self или cls. Если связь с классом не помогает пониманию, достаточно обычной функции модуля.
class Validator:
@staticmethod
def positive(text):
return int(text) > 0
print(Validator.positive('3'))
validator = Validator()
print(validator.positive('0'))
Ожидаемый вывод:
True FalseОфициальная документация: staticmethod
strТип / конструктор · Типы и преобразования
Создаёт строковое представление объекта или декодирует байты.
Нюанс. Для декодирования bytes нужна кодировка; str(bytes) без неё даёт представление байтов.
str(object=""); str(bytes, encoding, errors="strict") — упрощённо; остальные варианты в документации
Получить строковое представление либо явно декодировать байты. str(bytes) без encoding даёт представление байтов, а не раскодированный текст.
data = 'Привет'.encode('utf-8')
print(str(data, encoding='utf-8'))
print(str(42))
Ожидаемый вывод:
Привет 42Официальная документация: str
sumФункция · Числа
Суммирует элементы с начальным значением start.
Нюанс. Для соединения строк используется join(), поскольку строковый start здесь запрещён.
sum(iterable, /, start=0) — упрощённо; остальные варианты в документации
Сложить числовые значения с начальным итогом. Пустой источник возвращает start; для объединения строк используйте join, а не sum.
quantities = [2, 3, 4]
print(sum(quantities))
print(sum([], start=10))
Ожидаемый вывод:
9 10Официальная документация: sum
superТип / конструктор · Классы и атрибуты
Создаёт посредник для поиска следующей реализации в цепочке наследования.
Нюанс. Следует порядку MRO, поэтому следующий класс не обязательно является прямым родителем.
super(); super(type, object_or_type=None) — упрощённо; остальные варианты в документации
Обратиться к следующей реализации по MRO в методе наследника. Это механизм согласованного наследования, а не просто сокращение имени непосредственного родителя.
class Base:
def describe(self):
return 'база'
class Report(Base):
def describe(self):
return super().describe() + ': отчёт'
print(Report().describe())
Ожидаемый вывод:
база: отчётОфициальная документация: super
tupleТип / конструктор · Контейнеры
Создаёт неизменяемую последовательность из перебираемого объекта.
Нюанс. Вложенные изменяемые элементы остаются изменяемыми.
tuple(iterable=(), /) — упрощённо; остальные варианты в документации
Сохранить последовательность с неизменяемой внешней структурой. Вложенные списки остаются изменяемыми; tuple не выполняет глубокое замораживание данных.
source = [1, 2]
snapshot = tuple(source)
source.append(3)
print(snapshot)
print(tuple())
Ожидаемый вывод:
(1, 2) ()Официальная документация: tuple
typeТип / конструктор · Классы и атрибуты
Возвращает тип объекта или создаёт класс из имени, баз и словаря.
Нюанс. Для проверки с учётом наследования предпочтительнее isinstance().
type(object); type(name, bases, namespace, **kwargs) — упрощённо; остальные варианты в документации
Узнать точный тип или создать класс программно. Для проверки наследования обычно нужен isinstance; для обычного описания класса читаемее конструкция class.
Report = type('Report', (object,), {'limit': 3})
item = Report()
print(type(item).__name__)
print(item.limit)
print(type(7).__name__)
Ожидаемый вывод:
Report 3 intОфициальная документация: type
varsФункция · Области видимости
Возвращает __dict__ объекта, а без аргумента работает как locals().
Нюанс. У объектов без __dict__ возникает TypeError, например у некоторых экземпляров со __slots__.
vars(); vars(object, /) — упрощённо; остальные варианты в документации
Посмотреть __dict__ собственного объекта либо локальные имена без аргумента. Объекты со slots или без __dict__ могут не поддерживать этот вызов.
class Settings:
pass
settings = Settings()
settings.limit = 3
settings.mode = 'demo'
print(sorted(vars(settings).items()))
Ожидаемый вывод:
[('limit', 3), ('mode', 'demo')]
Официальная документация: varszipТип / конструктор · Итерация
Лениво объединяет элементы нескольких перебираемых объектов в кортежи.
Нюанс. По умолчанию обрезает до самой короткой последовательности; strict=True обнаруживает разную длину.
zip(*iterables, strict=False) — упрощённо; остальные варианты в документации
Сопоставить элементы нескольких источников по позиции. Обычно обход заканчивается на коротком; strict=True выявляет разную длину и доступен с Python 3.10.
pairs = list(zip(['A', 'B'], [1, 2], strict=True))
print(pairs)
try:
list(zip([1, 2], [3], strict=True))
except ValueError:
print('Разная длина')
Ожидаемый вывод:
[('A', 1), ('B', 2)]
Разная длина
Официальная документация: zip__import__Функция · Импорт
Вызывает низкоуровневый механизм импорта модуля.
Нюанс. Для обычного динамического импорта документация рекомендует importlib.import_module().
__import__(name, globals=None, locals=None, fromlist=(), level=0) — упрощённо; остальные варианты в документации
Обратиться к низкоуровневому импорту при исследовании механизма. В обычном коде используйте import, а для динамического имени чаще importlib.import_module.
module = __import__('math')
print(module.sqrt(9))
print(module.ceil(2.4))
Ожидаемый вывод:
3.0 3Официальная документация: __import__
Первичные источники главы:
10. Итераторы и генераторы: обрабатывать данные постепенно
Источник данных и текущий проход
Представьте отчёт из нескольких тысяч строк. Сначала нужно получить строку, затем проверить поля, посчитать сумму и перейти дальше. Хранить одновременно весь исходник и все промежуточные списки необязательно. Механизм итерации позволяет описать этот путь как последовательность небольших шагов.
Итерируемый объект умеет предоставить итератор. Список, строка и range являются такими источниками. Итератор хранит состояние текущего обхода и выдаёт следующий элемент через next. Получить его можно вызовом iter. Когда данные закончились, возникает StopIteration; обычный цикл for обрабатывает это завершение самостоятельно.
Разница становится заметна при повторном использовании. У списка можно запросить новый независимый проход. Один уже полученный итератор продолжает прежний проход и не возвращается в начало. Сам итератор при вызове iter обычно возвращает себя. Поэтому функция, которая принимает данные для нескольких проверок, должна знать, сможет ли она начать обход заново.
values = [10, 20, 30]
cursor = iter(values)
print(next(cursor))
print(list(cursor))
print(list(cursor))
print(list(iter(values)))
print(next(cursor, "Конец"))
10 [20, 30] [] [10, 20, 30] Конец
Генератор сохраняет место выполнения
Функция с yield создаёт генератор. Её тело начинает выполняться при запросе первого элемента, приостанавливается на yield и продолжает работу при следующем запросе. Локальные переменные сохраняются между этими шагами. Такой объект подходит для вычислений, где очередной результат можно получить отдельно от остальных.
Возвращаемое значение yield становится элементом потока. Return завершает генератор; он не создаёт ещё один элемент последовательности. Не выбрасывайте StopIteration вручную из его тела для обычного завершения: это может превратиться в RuntimeError. Достаточно дойти до конца функции или выполнить return.
Генераторное выражение похоже на comprehension списка, но заключено в круглые скобки. Оно удобно для суммы, поиска или последовательного преобразования. Ошибка тоже может возникнуть позже, во время потребления. Поэтому try должен охватывать фактический обход, если именно там происходит рискованное вычисление.
def running_total(values):
total = 0
for value in values:
total += value
yield total
stream = running_total([2, 3, 4])
print(next(stream))
print(list(stream))
print(list(running_total([])))
print(sum(value * value for value in range(4)))
2 [5, 9] [] 14
Ленивый конвейер и его ограничения
Разделите задачу на источник, фильтр, преобразование и потребителя. Например, строки файла превращаются в очищенные строки, непустые записи получают числа, итоговый потребитель считает сумму. Между этапами можно передавать генераторы вместо списков. Потребитель определяет, сколько результатов действительно понадобится.
Ленивость не означает отсутствие памяти вообще. Генератор сохраняет состояние; некоторые операции удерживают промежуточные данные. Сортировка требует получить элементы, прежде чем вернуть упорядоченный результат. Список из list(generator) снова собирает весь поток. Бесконечный источник нельзя без ограничений превращать в список.
Подумайте также о ресурсах. Генератор, читающий открытый файл, не должен возвращать управление после того, как внешняя with уже закрыла этот файл. Удобнее держать открытие внутри генератора и завершать обход в понятной области. При досрочном прекращении работы явно освобождайте принадлежащие вам ресурсы, а не рассчитывайте исключительно на уничтожение объекта.
Инструменты для соединения и группировки
itertools содержит операции над потоками. Chain последовательно соединяет источники, islice выбирает ограниченный фрагмент, batched собирает группы указанного размера. У каждой операции есть собственный контракт и минимальная версия. Например, batched появился в 3.12, а его строгая проверка последней неполной группы добавлена позже.
Groupby объединяет соседние элементы с одинаковым ключом. Это полезно для упорядоченных записей, но не является универсальным подсчётом всех совпадающих значений. Если одна категория встречается в нескольких несоседних местах, появятся несколько групп. Для общей частоты подойдёт Counter; для последовательных групп сначала отсортируйте данные по тому же ключу.
Группы groupby тоже являются итераторами и связаны с общим источником. Сохраните нужную группу, прежде чем перейти к следующей. Этот нюанс легко потерять, когда результат выглядит как пара из ключа и обычного списка. В коротком примере ниже list внутри цикла намеренно завершает обход каждой группы.
from itertools import chain, groupby, islice
records = [("Б", 2), ("А", 1), ("Б", 3)]
records.sort(key=lambda row: row[0])
grouped = []
for name, rows in groupby(records, key=lambda row: row[0]):
grouped.append((name, sum(row[1] for row in rows)))
print(grouped)
print(list(islice(chain([1, 2], [3, 4]), 3)))
[('А', 1), ('Б', 5)]
[1, 2, 3]
Функция как часть обработки
Map применяет функцию к поступающим значениям, filter оставляет подходящие, а comprehension часто выражает такой же шаг понятнее. Выбирайте форму, в которой видно намерение. Для нескольких источников map имеет свои правила завершения; в новых версиях есть строгая проверка длины. Не переносите параметры из примера другой версии без проверки.
Functools.partial позволяет заранее закрепить часть аргументов. Это полезно, когда одинаковая настройка передаётся во множество однотипных вызовов. Декоратор lru_cache сохраняет результаты для хешируемых аргументов. Его удобно применять к вычислению без изменяющихся внешних условий; функция чтения постоянно обновляемого файла требует более продуманного ключа и очистки.
Кэш удерживает аргументы и результаты, пока записи остаются в нём. Он экономит повторные вычисления, но не бесплатен по памяти. maxsize ограничивает число последних использованных комбинаций; cache_clear очищает сохранённое. В качестве первых наблюдений проверьте число реальных вычислений и состояние кэша, затем измеряйте скорость.
from functools import lru_cache, partial
calls = []
@lru_cache(maxsize=2)
def square(number):
calls.append(number)
return number * number
print([square(2), square(2), square(3)])
print(calls)
square.cache_clear()
print(square.cache_info().currsize)
base_two = partial(int, base=2)
print(base_two("101"))
[4, 4, 9] [2, 3] 0 5
Когда сохранить список, а когда оставить поток
Оставляйте поток, когда данные обрабатываются по порядку и нужен один проход. Сохраняйте список, когда потребуется сортировка, случайный доступ, повторное использование или небольшой снимок изменяющегося источника. Не объявляйте генератор более удачным в любой ситуации: для короткого набора обычный список может быть проще и достаточно экономным.
Перед переносом проверьте пустой источник, досрочную остановку и повторный вызов. Посмотрите, не расходует ли диагностический list уже нужный вам итератор. Для отладки маленькие данные можно сохранить намеренно, а в рабочей версии убрать только лишнее накопление. Главное — сохранить смысл результата и понятный жизненный цикл обработки.
Попробуйте самостоятельно. Вам нужен итог каждой соседней группы одинаковых статусов. Строки уже отсортированы. Почему list(groupby(...)) не сохраняет содержимое всех групп?
Посмотреть решение
Получится список пар с итераторами групп, связанными с общим проходом. Собирайте или агрегируйте каждую группу внутри цикла, как в примере groupby. Если нужны все записи категорий независимо от порядка, выберите словарь списков или сначала сортировку.
Первичные источники главы:
11. Исключения и ресурсы: понятная ошибка вместо скрытого сбоя
Начните с места и причины ошибки
Пользователь ввёл неправильное число, файл отсутствует или в ответе не хватает поля. Во всех случаях программе нужно сообщить, что произошло, и выбрать следующий шаг. Исключение передаёт сведения о неудаче вверх по цепочке вызовов, пока подходящий обработчик не возьмёт её на себя.
Traceback показывает путь к месту сбоя. Последняя строка содержит тип исключения и сообщение; предшествующие строки помогают найти операцию вашего кода. Начинайте с ближайшей относящейся к вашей программе строки. Название исключения указывает класс проблемы, но точная причина зависит от данных и контекста.
Для int неправильный текст приводит к ValueError. Попытка обратиться к отсутствующему ключу вызывает KeyError, несовместимые аргументы — TypeError. Не заменяйте все эти ситуации одной надписью «Ошибка». Посетителю нужна возможность исправить ввод, а разработчику — достаточная информация, чтобы повторить сбой.
values = ["12", "ошибка", ""]
for text in values:
try:
number = int(text)
except ValueError:
print("Введите целое число")
else:
print(number * 2)
24 Введите целое число Введите целое число
Обрабатывайте ожидаемую операцию
Try обозначает участок, где может возникнуть ожидаемая неудача. Except перехватывает подходящий тип; else выполняется, если этот участок завершился успешно. Перенос дальнейшей обработки в else помогает не принять её собственную ошибку за сбой исходного преобразования.
Используйте узкий обработчик вокруг понятного действия. Если проверяете текстовое число, перехватывайте ValueError. Исключение AttributeError в расчёте может указывать на ошибку программы, которую нельзя исправить предложением ввести число заново. Широкий except без осмысленного результата превращает полезную диагностику в неожиданное поведение.
Иногда удобно сначала проверить условие: например, недопустимое отрицательное количество. Иногда надёжнее выполнить действие и обработать неудачу. Проверка exists перед open не гарантирует, что файл останется доступен к моменту открытия. Такой выбор между предварительной проверкой и обработкой результата определяется свойствами операции, а не одним универсальным стилем.
Добавьте смысл на границе своего модуля
Собственное исключение позволяет описать проблему языком задачи. Например, модуль чтения заказа может сообщать InvalidRecord вместо внутреннего ValueError. Наследуйте прикладное исключение от Exception или подходящего обычного подкласса. BaseException включает также прерывание пользователем и другие события, которые обычно должны продолжать своё завершение.
Raise без нового объекта повторно передаёт текущее исключение. Запись raise NewError(...) from error сохраняет явную связь причин. Получатель видит понятный тип прикладной проблемы, а traceback позволяет найти исходную неудачу. В сообщении добавляйте идентификатор записи или название поля, но не подменяйте им всю причину.
Текст ошибки сам по себе не является стабильным API для управления ветвлением. Проверяйте тип и предусмотренные атрибуты исключения. Если нужно дополнить диагностическую информацию, с Python 3.11 доступен add_note. Не разбирайте чужую англоязычную строку по словам, когда есть явный тип FileNotFoundError или HTTP-статус.
class InvalidRecord(ValueError):
pass
def read_quantity(text):
try:
value = int(text)
except ValueError as error:
raise InvalidRecord("Поле quantity не является целым числом") from error
if value < 0:
raise InvalidRecord("Количество не может быть отрицательным")
return value
try:
read_quantity("не число")
except InvalidRecord as error:
print(type(error).__name__)
print(type(error.__cause__).__name__)
print(read_quantity("0"))
InvalidRecord ValueError 0
Finally и with завершают начатую работу
Finally выполняется при выходе из try независимо от обычного результата или исключения. В нём уместно освобождение принадлежащего вам ресурса. Но возвращать значение из finally опасно: это может заменить предыдущий return или скрыть исключение. Python 3.14 добавил предупреждения для некоторых таких выходов; понятнее держать итоговый return отдельно.
Контекстный менеджер объединяет вход в работу и выход из неё. В конструкции with объект получает возможность подготовить ресурс и завершить его использование. Для файла это обычно закрытие, для блокировки — освобождение. Ошибка внутри with не исчезает автоматически: менеджер должен явно решить, подавляет ли он её.
Для собственного контекста удобен contextlib.contextmanager. Код до yield готовит ресурс, значение yield передаётся в as, а очистка в finally выполняется после блока. Внутри такой генератор должен выдать одно значение. Если набор ресурсов определяется в ходе работы, ExitStack собирает контексты и закрывает их в обратном порядке.
from contextlib import contextmanager, ExitStack
@contextmanager
def resource(name):
print("Открыт", name)
try:
yield name
finally:
print("Закрыт", name)
try:
with ExitStack() as stack:
stack.enter_context(resource("А"))
stack.enter_context(resource("Б"))
raise ValueError("Учебная ошибка")
except ValueError:
print("Ошибка обработана")
Открыт А Открыт Б Закрыт Б Закрыт А Ошибка обработана
Несколько ошибок и отмена задачи
ExceptionGroup, доступная с Python 3.11, объединяет независимые исключения. Конструкция except* выбирает подходящие части группы. Она нужна, например, когда несколько конкурентных задач могут завершиться неудачей. Это отличается от обычного except, который рассматривает группу как один объект.
Необработанные части продолжают передаваться дальше. Поэтому заранее определите, какие ошибки можно исправить локально, а какие должны завершить всю операцию. В следующем примере обе категории обработаны, и программа успешно заканчивается. Число элементов проверяемо; оно не зависит от полного текста traceback.
Для асинхронной отмены нужен отдельный смысл. CancelledError связан с прекращением задачи; cleanup обычно должен выполниться, после чего отмена передаётся дальше. Если её незаметно подавить, TaskGroup или таймаут могут работать неожиданно. Подробный маршрут остановки разобран в главе о конкуренции.
try:
raise ExceptionGroup("Проверка записей", [
ValueError("Первое число"),
TypeError("Неподходящий объект"),
ValueError("Второе число"),
])
except* ValueError as errors:
print("Ошибок значения:", len(errors.exceptions))
except* TypeError as errors:
print("Ошибок типа:", len(errors.exceptions))
Ошибок значения: 2 Ошибок типа: 1
Как закончить обработку понятно
На границе интерфейса переведите техническую неудачу в следующий шаг: исправить поле, выбрать файл, повторить чтение. Внутри расчёта сохраняйте точную причину. Не выводите пользователю полный traceback вместо понятного сообщения, если он не может использовать его для исправления действия.
Запись ошибки в журнал не должна означать, что операция выполнена. Подтверждение показывают после успешного результата. Если после сбоя осталась частично записанная информация, заранее предусмотрите транзакцию, временный файл или другой способ получить согласованное состояние. Один except не превращает несколько действий в атомарную операцию.
Проверьте обычный ввод, неправильное значение и ошибку после открытия ресурса. Убедитесь, что программа не продолжает расчёт с выдуманным нулём, если ноль означает настоящий результат. Для внешнего ввода используйте явную проверку и raise: assert может отключаться с оптимизацией и предназначен для другого рода предположений.
Попробуйте самостоятельно. Функция должна читать quantity, отвергать отрицательные числа и сохранять причину ошибки преобразования. Где поставить try?
Посмотреть решение
Охватите int(text) отдельным try/except ValueError и передайте прикладное исключение через raise ... from error. Проверку отрицательного значения выполните после успешного преобразования. Этот маршрут уже реализован в read_quantity; добавьте самостоятельный вызов с "-1" и обработайте InvalidRecord.
Первичные источники главы:
12. Файлы, пути и кодировки
Запишите текст и прочитайте его обратно
Для первого опыта нужна своя учебная папка, а не настоящий рабочий документ. Следующий скрипт создаёт временный каталог, записывает русский текст и читает его обратно. После выхода из TemporaryDirectory каталог удаляется автоматически. Получается полный цикл: путь, данные, запись и проверка результата. При обычном запуске замените временную папку явно выбранным каталогом своего проекта; не оставляйте это решение случайной текущей директории.
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
path = Path(folder) / "note.txt"
path.write_text("Привет, Python!\n", encoding="utf-8")
print(path.read_text(encoding="utf-8"), end="")
print(path.name, path.suffix)
Привет, Python! note.txt .txt
Path описывает путь; он не создаёт файл при конструировании. Оператор / соединяет части пути с разделителями нужной платформы. Свойства name, suffix, stem и parent помогают разобрать его без ручной обработки слешей. Если важно знать число записанных символов, сохраните результат write_text. Это число символов текста, а не обязательно количество байтов на диске: кириллица в UTF-8 кодируется несколькими байтами.
Откуда считается относительный путь
Путь Path("data/input.txt") считается от текущей рабочей папки процесса, которую показывает Path.cwd(). Она может отличаться от папки скрипта: редактор, терминал и планировщик запускают программу по разным правилам. Поэтому инструкция «положите файл рядом» недостаточна. Для ресурса рядом с обычным скриптом можно взять Path(__file__).resolve().parent; для установленного пакета лучше использовать importlib.resources. В интерактивной оболочке переменной __file__ обычно нет.
Для пользовательского входного файла удобнее принимать путь параметром и показывать, какой файл выбран. Не меняйте рабочую папку через os.chdir только ради одного чтения: это влияет на остальные относительные пути программы и особенно затрудняет работу потоков. В Windows строка с обратными слешами может содержать управляющие последовательности. Используйте Path и отдельные компоненты либо raw-строку, помня, что raw-строка не может оканчиваться одиночным обратным слешем.
Текст — это символы, файл — это байты
Текстовый режим превращает байты в str с указанной кодировкой; двоичный режим rb возвращает bytes без такого преобразования. Для изображения или архива подходят read_bytes и write_bytes. Для текстового обмена явно указывайте encoding="utf-8". В Python 3.15 UTF-8 используется по умолчанию при обычной конфигурации, однако эту настройку можно отключить; явная кодировка также сохраняет договорённость при запуске в более старой среде.
Если получаете UnicodeDecodeError, сначала выясните формат исходного файла. У экспортов старых программ может быть другая кодировка; у UTF-8-файла с BOM полезно чтение через utf-8-sig. Режимы errors="ignore" и errors="replace" позволяют потерять или изменить символы, поэтому не годятся как автоматическое исправление отчёта. Сохраняйте исходник и выбирайте такую политику только там, где потеря текста явно допустима и заметна пользователю.
Читайте большой текст по строкам
read_text удобно для небольшого файла, который целиком помещается в память. Для журнала или длинной выгрузки используйте with path.open(...) и цикл по файловому объекту. Контекстный менеджер закроет файл и при успешном завершении, и при исключении. В примере считаются только непустые строки. Вызов strip() здесь применяется для проверки пустоты, а не для изменения сохраняемых данных: пробелы внутри исходной строки могут быть значимыми.
from pathlib import Path
count = 0
with Path("lines.txt").open(encoding="utf-8") as stream:
for line in stream:
if line.strip():
count += 1
print("Непустых строк:", count)
Непустых строк: 2
По умолчанию текстовое чтение нормализует распространённые окончания строк. При записи newline="\n" позволяет зафиксировать LF для переносимого текстового формата. Для CSV обычно задают newline="" и поручают разделители строк модулю csv. Если формат требует точного сохранения байтов, обработка должна идти в двоичном режиме: текстовая нормализация тогда лишняя. Нельзя бездумно делить CSV по переводам строки, поскольку поле в кавычках может само содержать перенос.
Запись, добавление и замена результата
Режим w обнуляет существующий файл при открытии; a дописывает в конец; x создаёт новый файл и выдаёт FileExistsError, если имя занято. Выберите поведение заранее. Для результата расчёта обычно полезнее сначала полностью сформировать текст и записать соседний временный файл, затем заменить целевой через replace. Так ошибка расчёта не оставит полупустой прежний отчёт. Это не универсальная гарантия сохранности при отключении питания: требования к синхронизации и файловой системе рассматриваются отдельно.
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
root = Path(folder)
target = root / "report.txt"
target.write_text("старый результат\n", encoding="utf-8")
temporary = root / "report.pending"
with temporary.open("x", encoding="utf-8", newline="\n") as stream:
stream.write("строк: 3\n")
temporary.replace(target)
print(target.read_text(encoding="utf-8"), end="")
print("Временный файл остался:", temporary.exists())
строк: 3 Временный файл остался: False
Сначала составьте список, затем обрабатывайте
mkdir(parents=True, exist_ok=True) создаёт папку и необходимые родительские каталоги. glob("*.txt") выбирает совпадающие имена, а rglob обходит вложенные каталоги. Не полагайтесь на порядок файловой системы: сортируйте список для воспроизводимого отчёта. Проверка is_file() исключит каталоги, однако символическая ссылка может вести к файлу за пределами выбранной папки. Политику работы со ссылками нужно задавать отдельно, особенно для внешних архивов и пользовательских каталогов.
from pathlib import Path
from tempfile import TemporaryDirectory
import shutil
with TemporaryDirectory() as folder:
root = Path(folder)
source, destination = root / "source", root / "copy"
source.mkdir()
destination.mkdir()
for name in ["b.txt", "a.txt", "notes.md"]:
(source / name).write_text(name, encoding="utf-8")
plan = sorted(path for path in source.glob("*.txt") if path.is_file())
print("План:", ", ".join(path.name for path in plan))
for path in plan:
shutil.copy2(path, destination / path.name)
print("Копий:", len(list(destination.glob("*.txt"))))
План: a.txt, b.txt Копий: 2
Обрабатывайте реальную ошибку открытия
Проверка exists() не гарантирует успешное последующее открытие: файл могут удалить, права могут измениться, а путь может указывать на каталог. Обрабатывайте FileNotFoundError, PermissionError или нужный OSError вокруг самой операции и сообщайте, что не получилось сделать. Не превращайте любую ошибку в «файл не найден»: причина важна для исправления. resolve() нормализует путь и разрешает ссылки, но сам по себе не запрещает выход за разрешённый каталог; такую границу программа проверяет отдельно.
Практика: отчёт без изменения исходников
Составьте список учебных TXT-файлов, отсортируйте его и посчитайте суммарное количество непустых строк. Сначала напечатайте план обработки, затем итог. Проверьте пустую папку и файл только из пробелов: оба случая должны давать ноль, а не ошибку. В решении исходные файлы не изменяются; отчёт не включает себя в список входных данных, потому что результат выводится отдельно.
Посмотреть решение
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
root = Path(folder)
(root / "a.txt").write_text("один\n\nдва\n", encoding="utf-8")
(root / "b.txt").write_text(" \n", encoding="utf-8")
paths = sorted(root.glob("*.txt"))
total = 0
for path in paths:
with path.open(encoding="utf-8") as stream:
total += sum(bool(line.strip()) for line in stream)
print("План:", ", ".join(path.name for path in paths))
print("Всего:", total)
print("Пустая папка:", sum(1 for _ in (root / "missing").glob("*.txt")))
План: a.txt, b.txt Всего: 2 Пустая папка: 0
Методы и свойства pathlib.PurePath и pathlib.Path
Все 57 публичных методов и 12 свойств с отдельными сигнатурами PurePath.* и Path.*; методы PurePath доступны также у Path. Конструкторы и специальные методы сюда не включены.
PurePath.as_posixМетод · Синтаксис пути
Возвращает текст пути с прямыми слешами.
Нюанс. Не преобразует Windows-путь в путь реально существующего POSIX-файла.
Официальная документация: PurePath.as_posixPurePath.is_absoluteМетод · Синтаксис пути
Проверяет полноту абсолютного пути.
Нюанс. В Windows нужны и корень, и диск либо UNC-ресурс; один начальный слеш недостаточен.
Официальная документация: PurePath.is_absolutePurePath.is_relative_toМетод · Синтаксис пути
Проверяет лексическую вложенность в другой путь.
Нюанс. Не проверяет файловую систему и не разрешает символические ссылки или сегменты .. .
Официальная документация: PurePath.is_relative_toPurePath.joinpathМетод · Синтаксис пути
Соединяет путь с дополнительными сегментами.
Нюанс. Абсолютный сегмент может заменить ранее заданную часть пути.
Официальная документация: PurePath.joinpathPurePath.full_matchМетод · Шаблоны
Проверяет весь путь по glob-шаблону.
Нюанс. Поддерживает рекурсивный **; регистр по умолчанию зависит от вида пути.
Официальная документация: PurePath.full_matchPurePath.matchМетод · Шаблоны
Проверяет путь по нерекурсивному glob-шаблону.
Нюанс. Относительный шаблон проверяется с правого края; ** здесь ведёт себя как *.
Официальная документация: PurePath.matchPurePath.relative_toМетод · Синтаксис пути
Строит относительный путь к указанной основе.
Нюанс. Без walk_up=True невложенный путь вызывает ValueError; операция остаётся лексической.
Официальная документация: PurePath.relative_toPurePath.with_nameМетод · Синтаксис пути
Создаёт путь с другим последним компонентом.
Нюанс. Файл не переименовывается; у пути без имени возникает ValueError.
Официальная документация: PurePath.with_namePurePath.with_stemМетод · Синтаксис пути
Создаёт путь с другим именем без последнего расширения.
Нюанс. У archive.tar.gz сохраняется только .gz, поэтому .tar не сохраняется как отдельное расширение.
Официальная документация: PurePath.with_stemPurePath.with_suffixМетод · Синтаксис пути
Создаёт путь с другим последним расширением.
Нюанс. Пустая строка удаляет расширение; никаких операций с файлом не происходит.
Официальная документация: PurePath.with_suffixPurePath.with_segmentsМетод · Расширение классов
Создаёт производный путь того же типа из сегментов.
Нюанс. Основное применение — перенос дополнительного состояния в подклассах PurePath.
Официальная документация: PurePath.with_segmentsPath.from_uriМетод · URI
Создаёт путь из URI схемы file.
Нюанс. Неверная схема или не-абсолютный путь вызывает ValueError.
Официальная документация: Path.from_uriPath.as_uriМетод · URI
Представляет абсолютный путь URI схемы file.
Нюанс. Относительный путь вызывает ValueError.
Официальная документация: Path.as_uriPath.homeМетод · Синтаксис пути
Возвращает домашний каталог пользователя.
Нюанс. Если домашний каталог определить нельзя, возникает RuntimeError.
Официальная документация: Path.homePath.expanduserМетод · Синтаксис пути
Раскрывает начальные ~ и ~user.
Нюанс. Неопределимый домашний каталог вызывает RuntimeError.
Официальная документация: Path.expanduserPath.cwdМетод · Синтаксис пути
Возвращает текущий рабочий каталог процесса.
Нюанс. Это рабочий каталог, а не обязательно каталог файла программы.
Официальная документация: Path.cwdPath.absoluteМетод · Синтаксис пути
Создаёт абсолютный путь относительно рабочего каталога.
Нюанс. Не нормализует .. и не разрешает символические ссылки.
Официальная документация: Path.absolutePath.resolveМетод · Синтаксис пути
Создаёт абсолютный путь с разрешёнными ссылками и сегментами .. .
Нюанс. При strict=False отсутствующий остаток пути сохраняется без проверки существования.
Официальная документация: Path.resolvePath.readlinkМетод · Ссылки
Читает целевой путь символической ссылки.
Нюанс. Относительная цель может остаться относительной; это не то же, что resolve().
Официальная документация: Path.readlinkPath.statМетод · Сведения
Получает свежие сведения о файле.
Нюанс. По умолчанию следует символическим ссылкам; follow_symlinks=False читает саму ссылку.
Официальная документация: Path.statPath.lstatМетод · Сведения
Получает сведения о самой символической ссылке.
Нюанс. Не переходит к целевому файлу ссылки.
Официальная документация: Path.lstatPath.existsМетод · Проверки
Проверяет существование объекта файловой системы.
Нюанс. False также возможен при недоступности или неверном пути; причины различают через stat().
Официальная документация: Path.existsPath.is_fileМетод · Проверки
Проверяет, является ли объект обычным файлом.
Нюанс. По умолчанию следует ссылкам; False не доказывает отсутствие объекта.
Официальная документация: Path.is_filePath.is_dirМетод · Проверки
Проверяет, является ли объект каталогом.
Нюанс. По умолчанию следует ссылкам; недоступный путь также может дать False.
Официальная документация: Path.is_dirPath.is_symlinkМетод · Проверки
Проверяет, является ли путь символической ссылкой.
Нюанс. Сломанная ссылка тоже возвращает True.
Официальная документация: Path.is_symlinkPath.is_junctionМетод · Проверки
Проверяет наличие Windows junction.
Нюанс. Такой тип соединения каталогов поддерживается только в Windows.
Официальная документация: Path.is_junctionPath.is_mountМетод · Проверки
Проверяет, является ли путь точкой подключения файловой системы.
Нюанс. В Windows учитывает корни дисков, UNC-ресурсы и подключённые каталоги.
Официальная документация: Path.is_mountPath.is_socketМетод · Проверки
Проверяет тип Unix socket.
Нюанс. Это проверка типа объекта файловой системы, а не доступности сетевого сервиса.
Официальная документация: Path.is_socketPath.is_fifoМетод · Проверки
Проверяет тип именованного канала FIFO.
Нюанс. False также возвращается для отсутствующего или недоступного пути.
Официальная документация: Path.is_fifoPath.is_block_deviceМетод · Проверки
Проверяет тип блочного устройства.
Нюанс. False также возвращается для отсутствующего или недоступного пути.
Официальная документация: Path.is_block_devicePath.is_char_deviceМетод · Проверки
Проверяет тип символьного устройства.
Нюанс. False также возвращается для отсутствующего или недоступного пути.
Официальная документация: Path.is_char_devicePath.samefileМетод · Проверки
Проверяет, указывают ли два пути на один файл.
Нюанс. Сравнивает файловые объекты, а не строки; недоступность может вызвать OSError.
Официальная документация: Path.samefilePath.openМетод · Чтение и запись
Открывает файл и возвращает объект потока.
Нюанс. Поток нужно закрыть, обычно конструкцией with.
Официальная документация: Path.openPath.read_textМетод · Чтение и запись
Читает весь файл как строку и закрывает его.
Нюанс. Весь текст загружается в память; кодировку можно задать явно.
Официальная документация: Path.read_textPath.read_bytesМетод · Чтение и запись
Читает весь файл как bytes и закрывает его.
Нюанс. Большой файл целиком занимает память.
Официальная документация: Path.read_bytesPath.write_textМетод · Чтение и запись
Записывает строку в файл и закрывает его.
Нюанс. Существующий файл перезаписывается; возвращает число записанных символов.
Официальная документация: Path.write_textPath.write_bytesМетод · Чтение и запись
Записывает bytes в файл и закрывает его.
Нюанс. Существующий файл перезаписывается; возвращает число записанных байтов.
Официальная документация: Path.write_bytesPath.iterdirМетод · Обход
Перебирает непосредственные дочерние пути каталога.
Нюанс. Порядок не гарантируется; это не рекурсивный обход.
Официальная документация: Path.iterdirPath.globМетод · Обход
Перебирает пути по glob-шаблону внутри каталога.
Нюанс. Возвращает объекты любого типа и не гарантирует порядок; для рекурсии применяют **.
Официальная документация: Path.globPath.rglobМетод · Обход
Рекурсивно перебирает пути по glob-шаблону.
Нюанс. Ошибки сканирования OSError подавляются, поэтому результат может быть неполным.
Официальная документация: Path.rglobPath.walkМетод · Обход
Перебирает дерево каталогов с именами каталогов и файлов.
Нюанс. По умолчанию не следует ссылкам; follow_symlinks=True способен привести к циклическому обходу.
Официальная документация: Path.walkPath.touchМетод · Создание
Создаёт файл либо обновляет его время изменения.
Нюанс. Существующий файл с exist_ok=True не обнуляется; exist_ok=False вызывает FileExistsError.
Официальная документация: Path.touchPath.mkdirМетод · Создание
Создаёт каталог и при необходимости недостающих родителей.
Нюанс. parents и exist_ok по умолчанию выключены; в 3.15 появился отдельный parent_mode.
Официальная документация: Path.mkdirPath.symlink_toМетод · Ссылки
Создаёт символическую ссылку в данном пути на target.
Нюанс. Порядок ссылка–цель противоположен os.symlink(); Windows учитывает target_is_directory.
Официальная документация: Path.symlink_toPath.hardlink_toМетод · Ссылки
Создаёт жёсткую ссылку в данном пути на target.
Нюанс. Порядок ссылка–цель противоположен os.link().
Официальная документация: Path.hardlink_toPath.copyМетод · Копирование и перемещение
Копирует файл или дерево каталогов в target.
Нюанс. По умолчанию не гарантирует сохранение всех метаданных; существующий целевой файл может быть заменён.
Официальная документация: Path.copyPath.copy_intoМетод · Копирование и перемещение
Копирует файл или дерево внутрь существующего каталога.
Нюанс. target_dir должен быть существующим каталогом; метод появился в 3.14.
Официальная документация: Path.copy_intoPath.renameМетод · Копирование и перемещение
Переименовывает файл или каталог и возвращает новый путь.
Нюанс. При существующей цели Unix и Windows ведут себя по-разному; относительная цель считается от cwd.
Официальная документация: Path.renamePath.replaceМетод · Копирование и перемещение
Переименовывает объект с заменой допустимой существующей цели.
Нюанс. Существующий файл или пустой каталог заменяется; относительная цель считается от cwd.
Официальная документация: Path.replacePath.moveМетод · Копирование и перемещение
Перемещает файл или дерево каталогов в target.
Нюанс. Между файловыми системами выполняется копирование с удалением; операция не становится атомарной.
Официальная документация: Path.movePath.move_intoМетод · Копирование и перемещение
Перемещает файл или дерево внутрь существующего каталога.
Нюанс. target_dir должен быть существующим каталогом; метод появился в 3.14.
Официальная документация: Path.move_intoPath.unlinkМетод · Удаление
Удаляет файл или символическую ссылку.
Нюанс. Каталоги не удаляет; missing_ok=True игнорирует только отсутствие файла.
Официальная документация: Path.unlinkPath.rmdirМетод · Удаление
Удаляет каталог.
Нюанс. Каталог должен быть пустым.
Официальная документация: Path.rmdirPath.ownerМетод · Права
Возвращает имя владельца файла.
Нюанс. Без модуля pwd операция не поддерживается; это существенно для Windows.
Официальная документация: Path.ownerPath.groupМетод · Права
Возвращает имя группы владельца файла.
Нюанс. Без модуля grp операция не поддерживается; это существенно для Windows.
Официальная документация: Path.groupPath.chmodМетод · Права
Меняет режим и права файла.
Нюанс. По умолчанию следует ссылкам; поддержка отдельных прав зависит от платформы.
Официальная документация: Path.chmodPath.lchmodМетод · Права
Меняет права самой символической ссылки.
Нюанс. Поддержка изменения прав ссылок зависит от платформы.
Официальная документация: Path.lchmodPurePath.partsСвойство · Свойства
Возвращает кортеж компонентов пути.
Нюанс. Диск и корень Windows объединены в первый компонент.
Официальная документация: PurePath.partsPurePath.parserСвойство · Свойства
Даёт модуль разбора пути posixpath или ntpath.
Нюанс. Выбор определяется видом пути, а не содержимым файловой системы.
Официальная документация: PurePath.parserPurePath.driveСвойство · Свойства
Возвращает диск или UNC-ресурс.
Нюанс. У POSIX-пути и некоторых Windows-путей строка пуста.
Официальная документация: PurePath.drivePurePath.rootСвойство · Свойства
Возвращает корневой компонент пути.
Нюанс. У Windows-пути C:folder корень отсутствует.
Официальная документация: PurePath.rootPurePath.anchorСвойство · Свойства
Возвращает объединение drive и root.
Нюанс. Непустой anchor сам по себе не гарантирует абсолютность Windows-пути.
Официальная документация: PurePath.anchorPurePath.parentsСвойство · Свойства
Даёт последовательность лексических родительских путей.
Нюанс. Не разрешает ссылки; поддерживает срезы и отрицательные индексы.
Официальная документация: PurePath.parentsPurePath.parentСвойство · Свойства
Возвращает непосредственный лексический родительский путь.
Нюанс. На корне или пустом пути не поднимается выше; .. предварительно не устраняется.
Официальная документация: PurePath.parentPurePath.nameСвойство · Свойства
Возвращает последний компонент без диска и корня.
Нюанс. У корня или самого UNC-ресурса может быть пустым.
Официальная документация: PurePath.namePurePath.suffixСвойство · Свойства
Возвращает последнее расширение с точкой.
Нюанс. Для archive.tar.gz это .gz; одиночная точка считается расширением с 3.14.
Официальная документация: PurePath.suffixPurePath.suffixesСвойство · Свойства
Возвращает список расширений имени.
Нюанс. Для archive.tar.gz это ['.tar', '.gz'], а не одно целое расширение.
Официальная документация: PurePath.suffixesPurePath.stemСвойство · Свойства
Возвращает последнее имя без последнего расширения.
Нюанс. У archive.tar.gz это archive.tar.
Официальная документация: PurePath.stemPath.infoСвойство · Свойства
Предоставляет объект кешируемых проверок типа файла.
Нюанс. Кеш может устареть; для свежей проверки используют Path.exists()/is_file()/is_dir().
Официальная документация: Path.infoМетоды и свойства Методы объекта Path.info
Все четыре метода официального протокола pathlib.types.PathInfo, получаемого через Path.info.
PathInfo.existsМетод · Кешируемые проверки
Проверяет существование через объект Path.info.
Нюанс. Результат может быть кеширован; follow_symlinks=False проверяет существование самой ссылки.
Официальная документация: PathInfo.existsPathInfo.is_dirМетод · Кешируемые проверки
Проверяет тип каталога через объект Path.info.
Нюанс. Результат может быть кеширован; обновление файловой системы не обязано сбросить кеш.
Официальная документация: PathInfo.is_dirPathInfo.is_fileМетод · Кешируемые проверки
Проверяет тип файла через объект Path.info.
Нюанс. Результат может быть кеширован; для актуальной проверки используйте Path.is_file().
Официальная документация: PathInfo.is_filePathInfo.is_symlinkМетод · Кешируемые проверки
Проверяет тип ссылки через объект Path.info.
Нюанс. Сломанная ссылка подходит, а результат может быть кеширован.
Официальная документация: PathInfo.is_symlinkПервичные источники главы:
13. Модули, пакеты и зависимости
Расчёт отдельно, запуск отдельно
Когда одна программа используется и в терминале, и в другом скрипте, полезно отделить расчёт от ввода и вывода. В примере учебный файл manual_prices.py содержит только функцию. Запускаемый скрипт импортирует её и печатает результат. Оба файла сохраняются в одной папке. Функция не спрашивает данные через input и не записывает отчёт: поэтому её можно независимо проверить, вызвать из интерфейса и повторно использовать для другой формы представления.
Файл модуля manual_prices.py:
def total(prices):
return sum(prices)
from manual_prices import total
print("Итого:", total([120, 80, 50]))
print("Пустой заказ:", total([]))
Итого: 250 Пустой заказ: 0
import manual_prices связывает имя модуля, и вызов выглядит как manual_prices.total(...). Вариант from manual_prices import total связывает выбранное имя непосредственно. Псевдоним через as помогает избежать конфликта, но не создаёт другую копию модуля. Избегайте from module import *: читателю сложнее понять происхождение имён, а обновление библиотеки может неожиданно изменить пространство имён. Начинайте с явных импортов стандартной библиотеки и своего проекта.
Почему импорт не должен запускать весь сценарий
Обычный импорт выполняет верхний уровень модуля при его первой загрузке. Определения функций выполняются как создание функций, но код внутри них ждёт вызова. Если наверху стоит запрос к сети, запись файла или бесконечный цикл, это произойдёт уже при импорте. Повторный импорт обычно использует объект из sys.modules; это кеш загруженных модулей, а не механизм постоянного перечитывания изменённого файла. Для обычной разработки после правки перезапускайте программу, не строя обновление приложения на reload.
У запускаемого файла имя __name__ равно "__main__". У импортированного модуля это его импортное имя. Условие if __name__ == "__main__": отделяет запуск сценария от доступных определений. Оно не отменяет остальной верхнеуровневый код, поэтому побочные действия всё равно стоит собрать в main(). Проверка особенно важна для процессов, которые заново импортируют главный модуль: иначе дочерний процесс может начать создавать новых детей ещё при загрузке.
import manual_app
import runpy
print("Импорт завершён")
print("Расчёт:", manual_app.calculate(4))
runpy.run_module("manual_app", run_name="__main__")
Импорт завершён Расчёт: 8 Запуск приложения: 6
Как Python находит ваш файл
Поиск модулей учитывает sys.path, установленное окружение и механизмы импорта. Поэтому файл json.py рядом с программой способен заслонить одноимённый стандартный модуль. Симптомом бывает неожиданное отсутствие метода или сообщение о частично инициализированном модуле. Проверьте имя своего файла, путь интерпретатора и module.__file__, если этот атрибут есть. Некоторые встроенные или особым образом загруженные модули не обязаны иметь обычный путь на диске; это не признак неисправности.
Не исправляйте ошибку установки пакета бесконечным добавлением случайных каталогов в sys.path. Сначала убедитесь, что зависимость поставлена именно для запускаемого интерпретатора, используя python -m pip. Различайте импортное имя и имя дистрибутива: они могут не совпадать. Версию установленного дистрибутива можно узнать через importlib.metadata.version; наличие его метаданных и наличие конкретного импортируемого модуля связаны, но отвечают на разные вопросы. При отсутствии дистрибутива возникает PackageNotFoundError.
Соберите связанные модули в пакет
Для первого обычного пакета создайте каталог с __init__.py, файлом расчёта и __main__.py для запуска. Пустой __init__.py подходит; не нужно импортировать в нём всё содержимое проекта. Относительный импорт from .core import total обращается к соседнему модулю внутри пакета. Прямой запуск такого файла по пути теряет контекст пакета и может вызвать ошибку относительного импорта. Запускайте пакет через python -m manual_report из правильного окружения.
import runpy
runpy.run_module("manual_report", run_name="__main__")
Отчёт: 42
Команда для обычного терминала; этот блок показывает способ запуска, а не отдельный проверенный скрипт:
python -m manual_report
Циклический импорт возникает, когда модули требуют имена друг друга до завершения инициализации. Часто помогает выделить общие типы и функции в третий модуль с минимальными зависимостями. Перенос импорта внутрь функции иногда устраняет конкретный порядок загрузки, но может скрыть неудачное устройство проекта. Старайтесь строить зависимости направленно: ввод вызывает расчёт, расчёт не импортирует интерфейс; слой хранения получает данные, а не управляет всем приложением. Такое разделение также делает тесты проще.
Ресурсы и окружение — разные части проекта
Для шаблона, встроенного в пакет, используйте importlib.resources.files и чтение через полученный объект ресурса. Не рассчитывайте, что пакет всегда лежит распакованной папкой рядом с текущей директорией. Если стороннему API действительно нужен физический путь, контекстный менеджер as_file предоставляет его на ограниченное время. Пользовательские отчёты, базы и настройки лучше хранить вне установленного пакета, в выбранной рабочей папке; после обновления программы они должны оставаться доступны.
pyproject.toml описывает сборку, метаданные и зависимости проекта. Сам файл не устанавливает пакеты и не создаёт окружение. Ограничение версии зависимости задаёт разрешённый диапазон; файл фиксации или иной воспроизводимый список закрепляет выбранные версии для конкретного запуска. Формат фиксации зависит от инструмента. Для передачи проекта также запишите требуемую версию Python и команды проверки. Не копируйте чужую виртуальную среду как универсальный переносимый пакет: воспроизводите её из описания зависимостей.
Отложенный импорт появился в Python 3.15
lazy import переносит загрузку модуля к первому использованию имени. Это бывает полезно для большого CLI с редко используемыми командами, но меняет момент возможной ошибки и выполнения побочных действий. Синтаксис разрешён на уровне модуля, а не внутри функции, класса или try/except/finally; звёздочный и future-импорт тоже не могут быть lazy. Для начала изучите обычный импорт. Совместимый путь __lazy_modules__ описан в документации, однако конкретный новый синтаксис потребует Python 3.15.
lazy import manual_missing_dependency_315
print("Объявление прошло")
try:
print(manual_missing_dependency_315.__name__)
except ModuleNotFoundError as error:
print("Первое использование:", type(error).__name__)
Объявление прошло Первое использование: ModuleNotFoundError
Практика: импорт без лишнего вывода
Положите функцию нормализации названия в модуль, импортируйте её и убедитесь, что при импорте ничего не печатается. Обработайте строку из пробелов как отсутствующее название. Затем добавьте в модуль main() и защиту запуска. Отдельно выполните модуль как программу: в этом случае демонстрация должна появиться. Так вы проверите два разных пользовательских сценария — повторное использование функции и самостоятельный запуск.
Посмотреть решение
from manual_names import normalize
print(normalize(" Новый отчёт "))
print(normalize(" "))
Новый отчёт Без названия
14. JSON, CSV, конфигурация, архивы и SQLite
Из CSV в проверенный JSON
Предположим, таблица содержит название позиции и количество. CSV подходит для табличного обмена, а JSON — для передачи структурированных данных программе. Между чтением и записью нужен отдельный шаг проверки. DictReader возвращает строки: число не станет целым автоматически. В примере мы проверяем набор колонок, непустое название и неотрицательное количество, а не принимаем любую успешно прочитанную запись. Для более подробной диагностики добавьте номер строки и перечень ошибок, прежде чем сохранять итог.
import csv
import json
from io import StringIO
source = StringIO("name,quantity\nКабель,3\nКлемма,0\n")
reader = csv.DictReader(source)
if reader.fieldnames != ["name", "quantity"]:
raise ValueError("Ожидаются колонки name и quantity")
records = []
for number, row in enumerate(reader, start=2):
if set(row) != {"name", "quantity"} or row["name"] is None or row["quantity"] is None:
raise ValueError(f"Неверное число полей в строке {number}")
name = row["name"].strip()
quantity = int(row["quantity"])
if not name or quantity < 0:
raise ValueError(f"Некорректная строка {number}")
records.append({"name": name, "quantity": quantity})
print(json.dumps(records, ensure_ascii=False, sort_keys=True))
[{"name": "Кабель", "quantity": 3}, {"name": "Клемма", "quantity": 0}]
Откройте настоящий CSV с encoding="utf-8" и newline="". Разделитель может быть запятой, точкой с запятой или табуляцией; задавайте delimiter по формату выгрузки. Не разбирайте файл через split(","): кавычки, запятые и переносы внутри поля разрушат таблицу. Для записи DictWriter получает явный список колонок; сначала вызывается writeheader. Сохраняйте исходный экспорт: ошибки строк легче исправлять, когда он не перезаписан преобразованным результатом.
JSON описывает данные, а не любые Python-объекты
json.loads читает JSON из строки; json.load — из открытого файлового объекта. Обратные операции — dumps и dump. JSON содержит объекты, массивы, строки, числа, логические значения и null. Python-кортеж после такого кругового преобразования станет списком; ключи JSON-объекта являются строками. Дата, Decimal и произвольный класс требуют заранее выбранного представления. Например, дату передают ISO-строкой и документируют её значение, а денежную сумму — строкой или целым числом минимальных единиц.
Успешный разбор JSON ещё не подтверждает нужную структуру. Проверьте тип верхнего значения, обязательные поля, диапазоны и версию схемы. В Python bool является подклассом int: проверка isinstance(value, int) примет True как количество. Если это недопустимо, используйте type(value) is int. Не подменяйте проверку assert, поскольку оптимизация может его отключить. Для обмена со строгими системами запрещайте NaN при записи через allow_nan=False; точность чисел также согласуют отдельно.
import json
def read_quantity(text):
value = json.loads(text)
if not isinstance(value, dict) or set(value) != {"quantity"}:
raise ValueError("Нужен объект только с полем quantity")
quantity = value["quantity"]
if type(quantity) is not int or quantity < 0:
raise ValueError("quantity: неотрицательное целое число")
return quantity
for text in ['{"quantity": 2}', '{"quantity": true}', '[]']:
try:
print("Количество:", read_quantity(text))
except ValueError as error:
print("Ошибка:", error)
Количество: 2 Ошибка: quantity: неотрицательное целое число Ошибка: Нужен объект только с полем quantity
Настройки TOML и INI
Модуль tomllib появился в Python 3.11 и читает TOML, но не записывает его. Файл для tomllib.load открывают в двоичном режиме; loads принимает текст. Для небольших настроек это удобно: числа и логические значения сохраняют понятные типы. Запись TOML требует другого инструмента либо собственного ограниченного шаблона. Не называйте сторонний TOML-писатель частью стандартной библиотеки и не генерируйте сложный формат простой склейкой непроверенного пользовательского текста.
import configparser
import tomllib
settings = tomllib.loads('[report]\nlimit = 20\nenabled = true\n')
print("TOML:", settings["report"]["limit"], settings["report"]["enabled"])
ini = configparser.ConfigParser(interpolation=None)
ini.read_string('[report]\nlimit = 20\nenabled = yes\n')
print("INI:", ini.getint("report", "limit"), ini.getboolean("report", "enabled"))
TOML: 20 True INI: 20 True
INI через configparser хранит значения преимущественно как строки; методы getint и getboolean выполняют явное преобразование. Интерполяция и преобразование регистра имён параметров способны влиять на результат, поэтому настройте их по формату. Настройки не обязательно содержат секреты: пароль лучше получать из предназначенного для этого источника окружения или хранилища. Независимо от формата проверяйте пределы после чтения: число 1000000 синтаксически допустимо, но может оказаться неприемлемым размером пачки.
SQLite: данные отдельно от текста SQL
SQLite хранит таблицы в одном файле и подходит для небольшого локального инструмента. Учебная база :memory: живёт только в соединении и не затрагивает файлы пользователя. Значения передавайте через параметры ?, а не f-строкой SQL. Это сохраняет кавычки внутри данных и отделяет их от команды. Параметрами нельзя подставить произвольное имя таблицы или колонки: такие имена выбирают из заранее разрешённых вариантов. Для серии однотипных вставок используйте executemany, для выборки — явно названные поля.
import sqlite3
connection = sqlite3.connect(":memory:", autocommit=False)
try:
with connection:
connection.execute("CREATE TABLE items (name TEXT NOT NULL, quantity INTEGER NOT NULL)")
connection.executemany("INSERT INTO items VALUES (?, ?)", [("Кабель", 3), ("O'Reilly", 1)])
rows = connection.execute("SELECT name, quantity FROM items WHERE quantity >= ? ORDER BY name", (1,)).fetchall()
for name, quantity in rows:
print(name, quantity)
finally:
connection.close()
O'Reilly 1 Кабель 3
Начиная с Python 3.12 режим транзакций можно явно задавать через autocommit=False; в Python 3.15 параметры соединения после database передают по имени. Блок with connection подтверждает успешную транзакцию или откатывает её при исключении, но не закрывает соединение. Поэтому закрытие вынесено в finally. Коммит не исправляет ошибки проверки данных и не гарантирует отсутствие конфликтов конкурентного доступа. Сначала проверьте строки, затем выберите осмысленную границу транзакции — например, один завершённый импорт.
Архивируйте свои результаты, осторожно извлекайте чужие
zipfile собирает несколько файлов в ZIP; gzip сжимает поток; TAR сохраняет набор записей и может использовать отдельное сжатие. В ZIP задавайте относительное arcname, чтобы не раскрывать абсолютный путь рабочего компьютера. При получении чужого архива проверяйте количество записей, размер распакованных данных, абсолютные пути, компоненты .. и ссылки. Недостаточно посмотреть размер самого ZIP: маленький архив может распаковаться в очень большой объём. В TAR используйте подходящий фильтр извлечения, не объявляя его полной заменой собственных ограничений.
from io import BytesIO
from zipfile import ZipFile, ZIP_DEFLATED
buffer = BytesIO()
with ZipFile(buffer, "w", compression=ZIP_DEFLATED) as archive:
archive.writestr("report/result.json", '{"count": 2}\n')
archive.writestr("README.txt", "Учебный отчёт\n")
with ZipFile(BytesIO(buffer.getvalue())) as archive:
print("Файлы:", ", ".join(sorted(archive.namelist())))
print("CRC:", archive.testzip() is None)
Файлы: README.txt, report/result.json CRC: True
Pickle, Base64 и struct решают разные задачи
pickle может восстанавливать Python-объекты, но загрузка недоверенного pickle способна выполнить код. Для входящих данных используйте формат с явной схемой, а не обещание отправителя «это всего лишь объект». base64 переводит байты в текстовое представление и не шифрует их. struct упаковывает числа в двоичный формат с заданной длиной и порядком байтов; для переносимого протокола эти параметры должны быть частью спецификации. Ни один из этих инструментов не заменяет проверку происхождения и смысла данных.
Практика: отвергните лишнее поле
Сделайте JSON-настройку с единственным полем limit: допустимы целые значения от 1 до 100. Проверьте значение 10, True, пустой объект и объект с дополнительным полем. Правильное решение различает ошибки схемы и ошибки диапазона, завершается успешно для демонстрации и не принимает логическое значение за единицу. Такая маленькая граница защищает последующий расчёт от неожиданного типа.
Посмотреть решение
import json
def parse_limit(text):
data = json.loads(text)
if not isinstance(data, dict) or set(data) != {"limit"}:
raise ValueError("схема")
if type(data["limit"]) is not int or not 1 <= data["limit"] <= 100:
raise ValueError("диапазон или тип")
return data["limit"]
for text in ['{"limit": 10}', '{"limit": true}', '{}', '{"limit": 2, "extra": 1}']:
try:
print(parse_limit(text))
except ValueError as error:
print(error)
10 диапазон или тип схема схема
15. Дата, время и часовые пояса
Сначала решите, что обозначает значение
Дата сдачи отчёта, местное время открытия и момент получения сообщения — разные данные. date содержит календарную дату, time — время суток, datetime — их сочетание, а timedelta описывает длительность. Не добавляйте часовой пояс к дате рождения только потому, что библиотека умеет работать со временем. Для события, происходящего одновременно для всех участников, напротив, нужен момент с понятным часовым поясом, иначе одна строка может обозначать разные реальные события.
from datetime import date, timedelta
import calendar
start = date.fromisoformat("2024-02-28")
print((start + timedelta(days=1)).isoformat())
print("Дней в феврале:", calendar.monthrange(2024, 2)[1])
try:
date.fromisoformat("2025-02-29")
except ValueError:
print("Такой даты нет")
2024-02-29 Дней в феврале: 29 Такой даты нет
Календарную дату удобнее разбирать специальным методом, чем вручную делить строку и отдельно проверять длину каждого месяца. fromisoformat проверяет существование даты, однако ваша программа всё равно задаёт допустимый диапазон и смысл. Дата в будущем синтаксически верна, но может не подходить для отчёта о завершённом событии. В интерфейсе показывайте человеку понятную ошибку и сохранённое введённое значение; при обмене данными документируйте ожидаемый ISO-формат, не зависящий от локали компьютера.
Naive и aware: задан ли смысл часового пояса
Объект datetime без информации о смещении называют naive. Сам по себе он не означает ни UTC, ни время вашего города. Aware-объект содержит подходящий tzinfo и может обозначать определённый момент. Для текущего UTC используйте datetime.now(timezone.utc); не сохраняйте его в строку, предварительно отбрасывая пояс. В новом коде не требуется utcnow(), который возвращает naive-объект и оставляет читателю необходимость угадывать договорённость. Сравнение порядка между naive и aware вызывает ошибку.
replace(tzinfo=...) назначает или меняет метку пояса, не пересчитывая показания часов. astimezone(...) переводит уже известный момент и меняет местные показания. Это различие важнее названия метода. Если исходник содержит «09:00 по Москве», сначала нужен корректный московский пояс. Если он содержит «09:00 UTC», метка +03:00 без преобразования сдвинет смысл события на три часа. Не исправляйте неизвестные входные данные простым добавлением UTC: сначала определите их контракт.
Один момент может иметь разные местные показания
В следующем самостоятельном примере используется фиксированное смещение +03:00, которое подходит для московского времени в выбранную учебную дату 12 октября 2026 года. Оно не заменяет историю и будущие правила региона. Обе записи обозначают один момент и сравниваются как равные. Разность между событием и более поздним событием вычисляется после приведения к UTC: такой расчёт показывает прошедшее реальное время, не смешивая его с изменением местных часов.
from datetime import datetime, timedelta, timezone
utc = datetime.fromisoformat("2026-10-12T09:00:00+00:00")
fixed_moscow = timezone(timedelta(hours=3))
local = utc.astimezone(fixed_moscow)
later = datetime.fromisoformat("2026-10-12T13:30:00+03:00")
print(local.isoformat())
print("Один момент:", local == utc)
elapsed = later.astimezone(timezone.utc) - utc
print("Минут:", int(elapsed.total_seconds() / 60))
2026-10-12T12:00:00+03:00 Один момент: True Минут: 90
Для региона используйте правила IANA
ZoneInfo("Europe/Moscow") представляет правила именованного региона, а не одно постоянное смещение. Модуль zoneinfo входит в стандартную библиотеку с Python 3.9, но сами данные IANA поступают из системы либо установленного пакета tzdata. В Windows подходящей системной базы может не быть, и тогда возникает ZoneInfoNotFoundError. Это проблема отсутствующих данных, а не неверного синтаксиса вашего расчёта. Версию базы важно учитывать для календаря и расписания, потому что правила определяются внешними решениями.
Следующий фрагмент зависит от наличия базы IANA, поэтому это инструкция для настроенного окружения, а не один из самодостаточных проверяемых скриптов главы. Установка tzdata выполняется отдельно в нужном окружении; библиотека не скачивает её сама. Если программа обязана работать без внешних данных, используйте документированное фиксированное смещение только для тех задач, где его ограничения действительно подходят. Не подменяйте отсутствующий региональный пояс молча произвольным смещением.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
moment = datetime(2026, 10, 12, 9, tzinfo=timezone.utc)
print(moment.astimezone(ZoneInfo("Europe/Moscow")).isoformat())
python -m pip install tzdata
Повторяющиеся часы, fold и пропущенное время
При переходе с летнего времени местные часы могут пройти один час дважды; при обратном переходе часть местных показаний вообще не существует. Атрибут fold различает два повторившихся времени: обычно 0 для первого и 1 для второго. Одного значения «02:30» недостаточно. Если вы принимаете местное расписание от человека, задайте правило выбора или попросите уточнить смещение. Простое конструирование datetime с ZoneInfo не является универсальной проверкой существования местного времени при переходе.
Для реальной длительности переводите границы в UTC перед вычитанием. При одинаковом объекте tzinfo арифметика datetime использует локальные показания и может не учитывать смену смещения так, как ожидается от секундомера. Для требования «каждый день в 09:00 по местному времени» нужен календарный расчёт с правилами пояса, а не прибавление 86400 секунд к прежнему UTC-моменту. Храните выбранный регион вместе с расписанием; для уже произошедшего события удобно хранить UTC и исходный контекст отображения.
Строка, timestamp и представление для человека
isoformat и fromisoformat полезны для машинного обмена. strftime и strptime работают с заданным шаблоном; названия месяцев могут зависеть от локали. Для timestamp важно знать единицу: секунды и миллисекунды различаются в тысячу раз. Чтение через datetime.fromtimestamp(value, timezone.utc) явно задаёт UTC; без аргумента пояса результат зависит от местной конфигурации. Для точного финансового или научного времени отдельно учитывают требования к точности и определение шкалы времени.
from datetime import datetime, timezone
moment = datetime.fromtimestamp(0, timezone.utc)
print(moment.isoformat())
label = datetime.strptime("12.10.2026", "%d.%m.%Y").date()
print(label.isoformat())
1970-01-01T00:00:00+00:00 2026-10-12
Измеряйте длительность монотонными часами
Календарные часы нужны для отметки события, но могут корректироваться системой. Для таймаута используйте time.monotonic, а для измерения выполнения — perf_counter или его вариант в наносекундах. Сравнивайте два показания одних и тех же часов; их абсолютное значение не предназначено для записи даты. В проверяемом примере выводится условие о неотрицательной длительности, а не нестабильное число секунд. Одно измерение не доказывает преимущество алгоритма: повторите опыт на осмысленных входных данных и учтите накладные расходы.
from time import perf_counter, monotonic
started = perf_counter()
result = sum(range(1000))
elapsed = perf_counter() - started
deadline = monotonic() + 5
print("Сумма:", result)
print("Длительность неотрицательна:", elapsed >= 0)
print("Дедлайн ещё впереди:", deadline > monotonic())
Сумма: 499500 Длительность неотрицательна: True Дедлайн ещё впереди: True
Практика: не принимайте время без пояса
Напишите функцию, которая принимает ISO-время, требует явное смещение и возвращает запись UTC. Проверьте корректную строку +03:00, naive-строку и ошибочный текст. Ошибки должны объяснять смысл отказа; для примера их ловят и печатают. Такая граница особенно полезна при импорте событий: неизвестное местное время не попадёт в базу под видом точно известного момента.
Посмотреть решение
from datetime import datetime, timezone
def normalize(text):
try:
value = datetime.fromisoformat(text)
except ValueError as error:
raise ValueError("Неверная ISO-строка") from error
if value.utcoffset() is None:
raise ValueError("Укажите часовой пояс")
return value.astimezone(timezone.utc).isoformat()
for text in ["2026-10-12T12:00:00+03:00", "2026-10-12T12:00:00", "ошибка"]:
try:
print(normalize(text))
except ValueError as error:
print(error)
2026-10-12T09:00:00+00:00 Укажите часовой пояс Неверная ISO-строка
Первичные источники главы:
16. Классы и dataclass: данные, состояние и понятные действия
Когда объект помогает понять программу
У заказа есть строки, количество и итог. Словарь способен хранить эти значения, но правила расчёта быстро расходятся по разным функциям. Класс позволяет собрать связанные данные и действия в одном месте. Начните с небольшой модели: какие значения у неё есть, что они означают и какие изменения разрешены.
Класс описывает устройство объектов, а экземпляр представляет конкретный объект. Два экземпляра одного класса могут иметь разные значения. Метод получает экземпляр первым аргументом; обычно его называют self. При вызове order.total() этот аргумент передаётся автоматически, поэтому в обычном месте вызова вы не пишете order ещё раз.
Не превращайте каждую функцию в класс только ради структуры. Отдельная функция удобна для преобразования значения без собственного состояния. Объект полезен, когда состояние и правила действительно связаны: например, позиция заказа, счётчик или настройка. Его публичные имена должны объяснять задачу посетителю API, а не порядок внутренних действий.
Начальные значения и проверка правил
В __init__ задают начальное состояние уже созданного экземпляра. Создание объекта связано с __new__; для большинства обычных моделей менять этот механизм не требуется. __init__ должен завершаться без возвращения произвольного результата. Если начальные данные нарушают правила, можно выбросить понятное исключение, и вызывающая сторона получит неудачу создания.
Разделяйте преобразование внешнего текста и проверку модели. Форма или CSV передаёт строку, затем отдельный шаг получает целое количество и Decimal цены. Модель проверяет допустимость уже подготовленных значений. Аннотации полезны для описания контракта, но сами не остановят передачу строки вместо числа.
Например, отрицательное количество может быть запрещено, а нулевое разрешено как пустая строка заказа. Такое решение зависит от предметной задачи. Не прячьте неверное значение автоматическим превращением в ноль: оно станет выглядеть как правильный расчёт. В примере правила записаны явно, а ошибочный случай проверяется отдельно.
from dataclasses import dataclass, field
from decimal import Decimal
@dataclass
class Product:
name: str
price: Decimal
def __post_init__(self):
if not isinstance(self.price, Decimal) or self.price < 0:
raise ValueError("Цена должна быть неотрицательным Decimal")
@dataclass
class Line:
product: Product
quantity: int
def __post_init__(self):
if type(self.quantity) is not int or self.quantity < 0:
raise ValueError("Количество должно быть неотрицательным целым")
@property
def total(self):
return self.product.price * self.quantity
@dataclass
class Order:
lines: list[Line] = field(default_factory=list)
@property
def total(self):
return sum((line.total for line in self.lines), Decimal("0"))
product = Product("Крепёж", Decimal("10.50"))
order = Order([Line(product, 2)])
print(f"{order.total:.2f}")
print(Order().total)
try:
Line(product, -1)
except ValueError:
print("Количество отклонено")
21.00 0 Количество отклонено
Атрибут класса и атрибут экземпляра
Запись self.name создаёт или меняет значение конкретного экземпляра. Имя, заданное непосредственно в теле класса, принадлежит классу и может быть общим для его объектов. Это удобно для неизменной настройки или константы, но общий изменяемый список часто становится неожиданностью.
Поиск атрибута может найти значение класса, если у экземпляра нет собственного. Присваивание self.items обычно создаёт собственный атрибут, а self.items.append(...) изменяет найденный список. Поэтому две похожие строки могут давать разные эффекты для остальных экземпляров. Общий список нельзя считать автоматически скопированным при создании объекта.
Создавайте личную коллекцию внутри __init__. В dataclass используйте field(default_factory=list): фабрика вызывается для каждого экземпляра. Для вложенных объектов продумайте также владение. Переданный снаружи список может остаться общим с вызывающей стороной; list(values) создаст новую внешнюю коллекцию, но не глубокую копию её элементов.
from dataclasses import dataclass, field
@dataclass
class Folder:
names: list[str] = field(default_factory=list)
first = Folder()
second = Folder()
first.names.append("report.csv")
print(first.names)
print(second.names)
print(first.names is second.names)
['report.csv'] [] False
Что dataclass делает за вас
Декоратор dataclass использует объявленные поля, чтобы подготовить типовые методы: создание экземпляра, читаемое представление и сравнение, если соответствующие возможности включены. Это удобно для записей данных, которые иначе потребовали бы повторять одинаковый код. Правила расчёта и валидацию вы задаёте отдельно.
После автоматически подготовленного __init__ можно выполнить __post_init__. Там уместны ограничения полей или получение производного значения. Но не смешивайте скрытый сетевой запрос с обычным созданием записи без ясной причины: вызывающий код должен понимать стоимость действия. Для работы с внешней системой лучше иметь отдельный явно названный шаг.
Frozen запрещает обычное присваивание полей экземпляра, а не любую возможную мутацию всех вложенных объектов. Список внутри frozen-записи остаётся списком. Если требуется неизменяемый набор элементов, выберите tuple или другой подходящий объект и проверьте вложенную структуру. Параметры eq, order, frozen, slots и kw_only меняют разные свойства; включайте их по потребности модели.
from dataclasses import dataclass, field, FrozenInstanceError
@dataclass(frozen=True)
class Draft:
title: str
tags: list[str] = field(default_factory=list)
draft = Draft("Отчёт")
draft.tags.append("учебный")
print(draft.tags)
try:
draft.title = "Другое"
except FrozenInstanceError:
print("Поле не переназначается")
['учебный'] Поле не переназначается
Свойства и альтернативные способы создания
Обычный метод работает с экземпляром. Classmethod получает класс и подходит для альтернативного конструктора, например from_text. Возвращайте cls(...), если такой конструктор должен корректно создавать и экземпляры подкласса. Staticmethod не получает автоматически ни экземпляр, ни класс; это обычная функция, размещённая рядом с моделью по смыслу.
Property предоставляет доступ как к атрибуту, сохраняя возможность вычисления или проверки. Пример order.total не хранит вторую независимую копию суммы, а рассчитывает её по строкам. Для меняемого свойства можно задать setter и сохранить проверку в одном месте.
Такой доступ должен оставаться понятным. Не скрывайте дорогую запись файла или запрос к серверу за чтением короткого свойства, если пользователь API ожидает простое получение значения. Имена save, fetch или recalculate лучше показывают самостоятельное действие. Для внутреннего значения часто используют префикс _, обозначающий соглашение о непубличном доступе.
class Stock:
def __init__(self, amount):
self.amount = amount
@staticmethod
def valid_amount(value):
return type(value) is int and value >= 0
@property
def amount(self):
return self._amount
@amount.setter
def amount(self, value):
if not self.valid_amount(value):
raise ValueError("Нужен неотрицательный остаток")
self._amount = value
@classmethod
def from_text(cls, text):
return cls(int(text))
stock = Stock.from_text("5")
print(stock.amount)
try:
stock.amount = -1
except ValueError:
print("Изменение отклонено")
print(stock.amount)
5 Изменение отклонено 5
Композиция и наследование
Order содержит Line, а Line содержит Product. Это композиция: один объект использует другой и может делегировать ему часть работы. Она хорошо показывает отношение «имеет»: заказ имеет строки. Изменение устройства Product не требует, чтобы Order становился его подклассом.
Наследование выражает отношение разновидности и общий контракт поведения. Подкласс переопределяет методы и может пользоваться базовыми. Убедитесь, что его можно применять в тех местах, где ожидается базовый объект. Если один вариант запрещает обычное действие родителя, вызывающему коду может потребоваться более точная модель.
Super ищет следующую реализацию согласно порядку MRO, а не всегда обращается к явно названному единственному родителю. Это особенно важно при множественном наследовании. Для первого проекта начните с небольшой иерархии или композиции; подробный пример совместных вызовов и описание MRO находятся в главе о протоколах.
Сравнение, представление и ограничение полей
Читаемое repr помогает исследовать объект в отладке. Пользовательская строка str может иметь другой формат, например название товара. Не включайте в диагностическое представление сведения, которые не должны попадать в журнал. Описывайте ровно столько состояния, сколько помогает узнать экземпляр.
Равенство задаёт смысл сравнения двух объектов. Если запись данных сравнивается по полям, два отдельно созданных экземпляра могут быть равны, хотя не являются одним объектом. При использовании в set или ключах dict добавляется требование к хешируемости. Автоматические методы dataclass зависят от его параметров; не включайте unsafe_hash ради обхода непонятной ошибки.
Slots ограничивает обычное хранение атрибутов экземпляра и иногда уменьшает расход памяти. Это не средство авторизации доступа и не универсальное ускорение программы. Enum подходит для узнаваемого ограниченного набора состояний, например черновик, готово, ошибка. Проверяйте эти состояния по смыслу, сохраняя отдельную обработку неизвестных внешних значений.
Как проверить модель перед расширением
Для новой модели перечислите допустимые начальные значения, публичные действия и ожидаемые ошибки. Проверьте два независимых экземпляра: изменяется ли второй, когда меняется первый? Затем рассмотрите пустую коллекцию, нулевое значение и запрещённый переход состояния. Эти небольшие опыты часто находят ошибку раньше, чем большая форма интерфейса.
Сохраняйте расчёт отдельно от печати, файлов и сети. Тогда Order.total можно проверить простым набором строк без браузера или базы. Внешний слой получает данные, вызывает модель и показывает результат только после успеха. Такой порядок облегчает обновление интерфейса и добавление нового способа загрузки.
Когда пример уверенно решает одну задачу, расширяйте контракт постепенно. Возможно, вам нужны неизменяемые позиции, округление суммы по правилу или сохранение заказа. Каждое такое решение меняет требования к модели и заслуживает собственного теста. Количество классов само по себе не делает программу понятнее; понятность появляется из согласованных имён и предсказуемых действий.
Попробуйте самостоятельно. Создайте два Order без аргументов, добавьте строку только в первый и сравните их totals. Почему второй должен остаться пустым?
Посмотреть решение
Поле lines использует default_factory=list, поэтому экземпляры получают независимые списки. Добавьте Line в first.lines; сумма first изменится, а second.total останется Decimal("0"). Если вложенные строки будут меняться, отдельно решите, какие объекты можно разделять.
Первичные источники главы:
17. Модель данных: как свои объекты участвуют в операциях Python
Операция задаёт ожидаемое поведение
Когда программа пишет len(value), for item in value или with value, она ожидает определённое поведение объекта. Протокол описывает, какие методы обеспечивают такую операцию и какой результат они должны возвращать. Это позволяет своему объекту работать в знакомых конструкциях без отдельного синтаксиса.
Методы с двойным подчёркиванием называют специальными. Для прикладного кода обычно понятнее вызвать обычную операцию, например len(box), чем напрямую box.__len__(). Интерпретатор учитывает правила протокола, включая поиск некоторых специальных методов на типе. Присваивание одноимённого атрибута только конкретному экземпляру не всегда меняет поведение оператора.
Перед добавлением метода запишите его контракт. Что означает длина? Какой элемент отдаётся по индексу? Когда заканчивается обход? Объект не должен внезапно читать сеть ради проверки bool, если вызывающий код считает это обычным быстрым вопросом. Согласованность операций важнее количества реализованных специальных имён.
Повторяемый источник и самостоятельный итератор
__iter__ должен предоставить итератор. У коллекции обычно каждый вызов создаёт новый проход; у самого итератора __iter__ возвращает self, а __next__ выдаёт следующий элемент или завершает обход через StopIteration. Такое различие объясняет, почему список обходится повторно, а уже исчерпанный генератор не начинается заново.
Для своего небольшого контейнера удобно сохранить tuple и вернуть iter(tuple). Если нужна вычисляемая последовательность, __iter__ может быть генераторной функцией. Выберите повторяемость намеренно: переданная внутрь одноразовая последовательность не станет автоматически повторяемой только потому, что вокруг неё появился класс.
Логика обхода не должна необъяснимо менять исходное содержимое. Если коллекция допускает изменение во время итерации, определите правило: снимок, запрет изменения или другой конкретный вариант. В учебном примере значения превращаются в tuple при создании, поэтому каждый посетитель API получает независимый проход по одному сохранённому набору.
class Values:
def __init__(self, values):
self._values = tuple(values)
def __iter__(self):
return iter(self._values)
def __len__(self):
return len(self._values)
def __getitem__(self, index):
return self._values[index]
values = Values([2, 4, 6])
print(list(values))
print(list(values))
print(len(values), values[1])
print(bool(Values([])))
[2, 4, 6] [2, 4, 6] 3 4 False
Длина, индексы и логическое значение
__len__ возвращает неотрицательное целое число. Когда __bool__ отсутствует, длина обычно определяет истинность объекта: нулевая коллекция считается ложной. Собственный __bool__ должен возвращать bool. Это полезно, когда понятие «есть значение» отличается от количества элементов, но новое правило нужно объяснить пользователю класса.
__getitem__ связывает объект с обращением по ключу или индексу. Для последовательности отсутствие позиции выражается IndexError, для отображения отсутствие ключа — KeyError. Если поддерживаете срезы, получите объект slice и обработайте его или передайте внутренней последовательности. В примере выше tuple уже знает правила положительных и отрицательных индексов.
Не считайте наличие одного метода полным соответствием большому протоколу. Контейнеру могут потребоваться другие операции и корректная семантика ошибок. Абстракции collections.abc помогают назвать ожидаемую разновидность: Iterable, Iterator, Sequence, Mapping. После такого выбора проверьте не только isinstance, но и реальные действия, которые будет выполнять вызывающий код.
Равенство и хеш должны согласовываться
__eq__ отвечает за смысл равенства. Два объекта с одинаковыми данными могут быть равны, оставаясь разными экземплярами. Сравнение с неподдерживаемым типом может вернуть NotImplemented, давая Python возможность использовать другой предусмотренный путь. Это специальный результат, который отличается от исключения NotImplementedError.
Хешируемый объект должен сохранять устойчивый хеш во время использования ключом, а равные объекты должны иметь одинаковые хеши. Поэтому изменяемую модель не стоит превращать в ключ только ради устранения TypeError. Если значения, определяющие равенство, поменялись, поиск в словаре может нарушиться.
Dataclass с frozen=True и подходящими полями способен предоставить согласованные операции автоматически. Но вложенный нехешируемый объект всё равно создаст проблему. Проверяйте весь состав ключа. Для типового идентификатора часто достаточно неизменяемой строки или tuple; отдельный класс нужен, когда у ключа есть дополнительный смысл и контракт.
from dataclasses import dataclass
@dataclass(frozen=True)
class Key:
project: str
number: int
first = Key("demo", 1)
second = Key("demo", 1)
values = {first: "готово"}
print(first == second)
print(first is second)
print(values[second])
print(len({first, second}))
True False готово 1
Контекстный менеджер класса
__enter__ выполняет подготовку и возвращает значение для as. __exit__ получает информацию о завершении блока и освобождает ресурс. Его истинный результат подавляет исключение, поэтому не возвращайте True случайно. Для обычной очистки уместно False или None, чтобы ошибка продолжила передаваться вызывающей стороне.
Если сам __enter__ завершился ошибкой, последующий __exit__ для этого входа не вызывается. Ресурсы, уже открытые внутри неудачного __enter__, нужно закрыть в его собственной логике. При нескольких независимых открытиях помогает ExitStack. Это конкретная граница жизненного цикла, которую важно проверить намеренным сбоем.
В примере ниже объект запоминает факт завершения. Реальная модель могла бы закрывать собственный файл или соединение, но учебный скрипт не трогает файлы пользователя. Событие внутри блока обработано снаружи; это доказывает, что менеджер завершил работу и сохранил исключение, а не выдал ложный успешный результат.
class Session:
def __init__(self):
self.closed = False
def __enter__(self):
print("Вход")
return self
def __exit__(self, error_type, error, traceback):
self.closed = True
print("Выход")
return False
session = Session()
try:
with session as current:
print(current is session)
raise ValueError("Учебный сбой")
except ValueError:
print("Исключение сохранено")
print(session.closed)
Вход True Выход Исключение сохранено True
Дескриптор связывает поле с поведением
Дескриптор — объект в классе, который реализует __get__, __set__ или __delete__. Он может управлять чтением и записью поля экземпляра. Property является знакомым применением этого механизма. Собственный дескриптор полезен, когда одна и та же проверка повторяется у многих полей или моделей.
__set_name__ сообщает имя, под которым дескриптор находится в создаваемом классе. Это позволяет выбрать внутреннее поле для хранения. При обращении через класс __get__ получает instance=None; часто удобно вернуть сам дескриптор. При обращении через экземпляр он возвращает значение по своему правилу.
Не храните значение каждого экземпляра одним обычным атрибутом самого дескриптора: дескриптор принадлежит классу и разделяется его объектами. В следующем примере данные сохраняются в отдельных экземплярах под внутренним именем. Проверка выполняется до изменения, поэтому неправильный ввод оставляет прежнее значение.
class NonNegative:
def __set_name__(self, owner, name):
self.storage = "_" + name
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.storage)
def __set__(self, instance, value):
if type(value) is not int or value < 0:
raise ValueError("Нужно неотрицательное целое")
setattr(instance, self.storage, value)
class Stock:
amount = NonNegative()
def __init__(self, amount):
self.amount = amount
first = Stock(5)
second = Stock(2)
try:
first.amount = -1
except ValueError:
print("Запись отклонена")
print(first.amount, second.amount)
Запись отклонена 5 2
Перехват атрибутов требует аккуратности
__getattr__ вызывается, когда обычный поиск атрибута завершился неудачей. Это подходит для обоснованного запасного пути. __getattribute__ участвует в каждом обычном обращении и требует большей осторожности. Внутренний доступ через self.name в таком методе способен снова вызвать тот же метод и породить рекурсию.
Чтобы делегировать обычный поиск, используют object.__getattribute__(self, name) или подходящий super. Если атрибут отсутствует, сохраняйте понятный AttributeError. Универсальный возврат None для любого опечатанного имени мешает обнаружить ошибки программы и меняет поведение hasattr.
Порядок поиска учитывает дескрипторы, атрибуты экземпляра и классы в MRO. Дескриптор с операцией записи имеет приоритет, который отличается от обычного атрибута класса. Для первого компонента обычно хватит property или явного метода; перехват всего доступа нужно вводить после конкретного требования и проверки поведения отладки.
Super продолжает цепочку MRO
При множественном наследовании Python строит порядок разрешения методов. Super продолжает поиск после текущего класса в этой цепочке. Поэтому общая базовая реализация может быть вызвана один раз, если участники согласованно делегируют действие дальше. Прямые вызовы нескольких базовых классов иногда повторяют общий шаг.
Методы совместной цепочки должны иметь совместимые сигнатуры и договориться о передаче аргументов. Если один участник не вызывает super, продолжение заканчивается на нём. Это может быть намеренным решением, но его нужно понимать. Вывод следующего примера отражает порядок A, B, Base, заданный MRO класса Combined.
Посмотреть порядок можно через __mro__ или mro(). Эти инструменты помогают объяснить найденный метод, но не выбирают архитектуру вместо вас. Если сложная иерархия нужна только для переиспользования одного расчёта, попробуйте выделить функцию или включить вспомогательный объект через композицию.
class Base:
def describe(self):
return ["Base"]
class A(Base):
def describe(self):
return ["A"] + super().describe()
class B(Base):
def describe(self):
return ["B"] + super().describe()
class Combined(A, B):
pass
print(" > ".join(Combined().describe()))
print([item.__name__ for item in Combined.__mro__])
A > B > Base ['Combined', 'A', 'B', 'Base', 'object']
Создание класса, ABC и метакласс
Метакласс участвует в создании самого класса; для обычного class базовым механизмом является type. Он может проверить определение или подготовить общий интерфейс. Но такая логика влияет на все создаваемые классы и усложняет объяснение ошибок. Применяйте её к конкретному требованию библиотеки, а не как обязательный этап каждой модели.
Для более узких задач существуют __init_subclass__, дескрипторы и декораторы классов. Например, нужно проверить свойство каждого подкласса или зарегистрировать его имя. Сначала рассмотрите такой ограниченный механизм: в нём легче увидеть, когда проверка выполняется и какое состояние сохраняется.
ABC помогает объявить абстрактные операции и не создавать неполный экземпляр. Protocol из typing описывает структурное ожидание для статического анализа. Это разные инструменты: наследование от базового класса и соответствие набору методов не тождественны. Runtime-проверка требует собственного понимания того, что именно она проверяет.
Жизненный цикл объекта и границы CPython
Модель данных Python не обещает единый момент уничтожения объектов для всех реализаций. Особенности подсчёта ссылок в CPython не стоит превращать в договорённость о закрытии файла. __del__ может выполняться в неудобном контексте; ошибки в нём и сложные зависимости делают его плохим основным маршрутом освобождения ресурса.
Для принадлежащего программе ресурса используйте явное завершение или with. Weakref позволяет хранить ссылку, которая сама не удерживает объект живым; не каждый тип поддерживает такой механизм. Слабая ссылка полезна для кэша или наблюдения, но не отменяет проверки, что объект ещё существует при использовании.
Перед переносом собственного протокола составьте набор реальных операций: повторный обход, пустое значение, неизвестный ключ, сравнение с другим типом, ошибка внутри with. Эти проверки раскрывают смысл интерфейса лучше, чем перечень специальных имён. Для деталей конкретного метода переходите в официальную модель данных и связывайте правило с наблюдаемым результатом.
Попробуйте самостоятельно. У дескриптора есть self.value, в котором хранится остаток. Почему изменение одного Stock может затронуть другой?
Посмотреть решение
Один объект дескриптора размещён в классе и обслуживает все экземпляры. Храните значения в самом instance, как в примере с _amount, или используйте осмысленную отдельную структуру хранения. После отказа записи проверьте оба экземпляра.
Первичные источники главы:
18. Аннотации и typing: описать контракт и проверить данные
Подсказка о типе и настоящая проверка
Когда функция принимает количество и возвращает итог, аннотация помогает увидеть договорённость сразу: какие значения предполагаются и какой результат ожидается. Редактор или отдельный анализатор может найти несовместимый вызов до выполнения. Но интерпретатор обычно не проверяет эти договорённости автоматически.
Запись def double(value: int) -> int описывает контракт. Если передать строку и тело выполнит value * 2, Python может получить повторённый текст. Аннотация не превратит его в int. Следующий пример намеренно показывает ошибочное использование: статический анализ должен обратить на него внимание, а runtime демонстрирует действительное поведение.
Для входа из формы, файла или сети нужен отдельный парсер и проверка. На этой границе можно превратить строку в число, отвергнуть недопустимое значение и создать объект нужной структуры. После успешной проверки аннотации помогают последующим функциям пользоваться понятным контрактом без повторения одного и того же разбора.
def double(value: int) -> int:
return value * 2
result = double("4")
print(result)
print(type(result).__name__)
44 str
Контейнеры, объединения и отсутствие значения
В Python 3.9+ стандартные контейнеры можно записывать как list[str] и dict[str, int]. Такая запись описывает элементы, а не проверяет их при append. Для объединения типов с Python 3.10 используется вертикальная черта: int | str. Возможность None обозначают как T | None или Optional[T].
Optional не означает, что аргумент разрешено не передавать. Необязательность вызова определяется наличием default в сигнатуре. Например, value: int | None без значения по умолчанию остаётся обязательным аргументом. Отдельно объясняйте тип значения и форму вызова, чтобы посетитель не смешивал эти правила.
Object означает самый общий объект с доступными общими операциями; анализатор всё равно потребует проверку перед специфичным действием. Any разрешает анализу меньше проверять операции, поэтому большое число Any может спрятать полезные ошибки. Cast сообщает предположение анализатору, а не выполняет преобразование или валидацию. Для строки из файла используйте действительный int(text), если нужен int.
TypedDict описывает структуру словаря
TypedDict подходит для записи с известными именами ключей. Внешне это обычный dict; модель типов объясняет, что у quantity ожидается int, а у title — str. Required и NotRequired позволяют описать обязательность отдельных ключей. Это тоже договорённость статического анализа, а не автоматически запускаемый валидатор.
Проверьте внешние данные перед возвратом TypedDict из функции. Ни наличие ключа, ни isinstance(raw, dict) не подтверждают тип каждого значения. Для количества отдельный вопрос — допустим ли bool, ведь bool является подтипом int. В данном примере требуется обычное неотрицательное целое число, и проверка намеренно использует точный тип.
В Python 3.15 появились возможности TypedDict для описания дополнительных полей, включая typed extra items. Это помогает точнее задать открытость структуры. При этом runtime-парсер всё равно должен решить, игнорирует он неизвестные поля или отвергает их. Поддержка нового правила конкретным анализатором проверяется отдельно от версии интерпретатора.
from typing import TypedDict
class Row(TypedDict):
title: str
quantity: int
def parse_row(raw: dict[str, object]) -> Row:
title = raw.get("title")
quantity = raw.get("quantity")
if not isinstance(title, str) or not title.strip():
raise ValueError("Нужен заголовок")
if type(quantity) is not int or quantity < 0:
raise ValueError("Нужно неотрицательное целое количество")
return {"title": title.strip(), "quantity": quantity}
row = parse_row({"title": " Кабель ", "quantity": 2})
print(row["title"], row["quantity"])
try:
parse_row({"title": "Кабель", "quantity": True})
except ValueError:
print("Ввод отклонён")
Кабель 2 Ввод отклонён
Protocol и границы runtime-проверки
Protocol описывает структурное ожидание: объект должен иметь определённые методы и свойства. Подходящий класс может соответствовать ему без явного наследования. Это удобно для функции, которой нужен читающий объект или источник длины; она зависит от требуемого поведения, а не от одной конкретной реализации.
Декоратор runtime_checkable разрешает некоторые проверки isinstance для такого протокола. Они проверяют предусмотренную структуру, а не полную корректность сигнатур и возвращаемых значений. Если объект имеет __len__, но возвращает строку, проверка структуры может пройти, а реальная операция len завершится TypeError.
Для уверенного интерфейса сочетайте описание протокола, статическую проверку и тест реального поведения. Если операция может завершиться неудачей из-за состояния или ввода, продумайте исключение. Protocol не обещает успешное чтение файла, наличие сети или корректный внутренний алгоритм. Эти условия относятся к конкретной реализации и задаче.
from typing import Protocol, runtime_checkable
@runtime_checkable
class HasLength(Protocol):
def __len__(self) -> int:
...
class WrongLength:
def __len__(self):
return "два"
print(isinstance([1, 2], HasLength))
value = WrongLength()
print(isinstance(value, HasLength))
try:
len(value)
except TypeError:
print("Структура не гарантирует результат")
True True Структура не гарантирует результат
Связанные типы и уточнение после условия
Параметр типа помогает связать вход и выход. Например, функция возвращает элемент той же разновидности, что находится в переданной последовательности. Для старого синтаксиса применяют TypeVar и Generic; с Python 3.12 доступно объявление параметров типов прямо у функции или класса. Обычный type alias с ключевым словом type тоже требует 3.12+.
Overload описывает анализатору несколько форм вызова, но реальное тело функции всё равно должно обработать их. Literal задаёт ограниченные значения, а не только широкий тип str. Не стройте сложный набор overload ради функции, чья обычная сигнатура уже ясно показывает поведение.
TypeGuard и TypeIs помогают уточнить тип после пользовательской проверки. TypeGuard обычно уточняет положительную ветвь; TypeIs, появившийся в 3.13, имеет более строгие требования к совместимости и учитывает также отрицательную. Указанная проверка обязана быть правдивой: аннотация предиката не исправит его неверную реализацию.
Python 3.15 добавляет TypeForm для описания значений, представляющих формы типов, и другие расширения статической системы. Такие возможности уместны в библиотеках, работающих с самими описаниями типов. Для первого проекта достаточно ясных входных значений, контейнеров и результата; расширяйте запись, когда новый контракт действительно требует этого.
Отложенные аннотации и интроспекция
В Python 3.14 обычные аннотации стали вычисляться отложенно. Это меняет работу кода, который читает их во время выполнения. Annotationlib предоставляет форматы чтения: значения, представления с незавершёнными ссылками и строки. Выберите формат под задачу, а не рассчитывайте, что любой ещё не объявленный тип уже существует.
В следующем примере имя Missing намеренно не определено. Описание в строковом формате доступно, но запрос действительных значений требует разрешить это имя. Поэтому ошибка возникает при чтении VALUE. Этот пример рассчитан на Python 3.14+, а не на старый интерпретатор, который может остановиться уже при определении функции.
Декоратор или библиотека, которая исследует аннотации, должна учитывать версию и выбранный формат. Получение runtime-значений может выполнять предусмотренную логику оценки выражений. Не считайте содержимое аннотаций автоматически безопасным и уже проверенным внешним набором данных. Для прикладной валидации используйте отдельный явный контракт.
from annotationlib import Format, get_annotations
def convert(value: Missing) -> int:
return 1
print(sorted(get_annotations(convert, format=Format.STRING)))
try:
get_annotations(convert, format=Format.VALUE)
except NameError:
print("Нужно определить Missing")
['return', 'value'] Нужно определить Missing
Попробуйте самостоятельно. Функция получает JSON с quantity. Достаточно ли написать quantity: int или cast(int, value)?
Посмотреть решение
Нет. Проверьте наличие и фактический тип, задайте правило для bool и отрицательных чисел, затем верните подготовленную запись. Аннотация описывает результат этого шага. Образец parse_row показывает такую границу; добавьте испытание с отсутствующим ключом.
Первичные источники главы:
19. Тесты, отладка, сообщения и читаемый код
Проверяйте обещанное поведение
Тест нужен там, где важно доказать результат и удержать его после изменения программы. Начните с маленькой функции без файлов и сети: её входы и выходы легко повторить. Для преобразования количества существенны обычное число, ноль, пустая строка и ошибочное значение. Не копируйте внутрь теста весь алгоритм функции: две одинаковые ошибки могут согласиться друг с другом. Ожидаемый результат следует из условия задачи, а не из текущей реализации, которую вы собираетесь проверить.
import io
import unittest
def quantity(text):
try:
value = int(text)
except ValueError as error:
raise ValueError("Введите целое число") from error
if value < 0:
raise ValueError("Количество не может быть отрицательным")
return value
class QuantityTests(unittest.TestCase):
def test_regular(self):
self.assertEqual(quantity(" 3 "), 3)
def test_zero(self):
self.assertEqual(quantity("0"), 0)
def test_invalid(self):
for text in ["", "abc", "-1"]:
with self.subTest(text=text):
with self.assertRaises(ValueError):
quantity(text)
suite = unittest.defaultTestLoader.loadTestsFromTestCase(QuantityTests)
result = unittest.TextTestRunner(stream=io.StringIO()).run(suite)
print("Тестов:", result.testsRun)
print("Успешно:", result.wasSuccessful())
Тестов: 3 Успешно: True
Методы assertEqual и assertRaises принадлежат unittest.TestCase; они формируют понятный отчёт при несовпадении. Это не тот же механизм, что оператор assert языка. Обычный assert подходит для внутренних предположений во время разработки, но может исчезнуть при запуске с -O. Проверка пользовательского ввода должна оставаться явным условием и исключением. Также не помещайте необходимую запись файла или изменение состояния внутрь assert: отключение проверки тогда изменит само поведение программы.
От ошибки к проверке исправления
Для найденной ошибки сначала сохраните минимальные входные данные, которые её вызывают. Затем сформулируйте правильный результат, добавьте тест и проверьте, что прежняя реализация действительно его не проходит. Исправление должно пройти эту проверку вместе с соседними существенными сценариями. Такой тест называют регрессионным: он защищает от возвращения известной проблемы. Не пытайтесь покрыть бессмысленной проверкой каждую строчку. Полезнее проверить границы, пустые данные, исключения, порядок и сохранение состояния там, где это меняет результат.
Команда терминала для проекта с тестовыми файлами; путь и структура задаются вашим проектом, поэтому этот блок не является самостоятельным исполняемым примером главы:
python -m unittest discover -s tests -v
Подменяйте границу, а не сам расчёт
Функция, которая и скачивает данные, и считает сумму, сложнее проверяется без сети. Передайте источник данных параметром: настоящий запуск использует нужную функцию чтения, тест — маленькую подмену. unittest.mock умеет записывать вызовы, задавать результаты и вызывать заданное исключение. Если применяется patch, подменяйте имя там, где его ищет проверяемый код, а не автоматически в модуле первоначального определения. Излишне сложная система моков часто указывает на слишком тесно связанные части программы.
from unittest.mock import Mock
def sum_report(load_rows):
return sum(row["quantity"] for row in load_rows("demo"))
source = Mock(return_value=[{"quantity": 2}, {"quantity": 3}])
print("Сумма:", sum_report(source))
source.assert_called_once_with("demo")
print("Вызов источника проверен")
failing = Mock(side_effect=OSError("учебная ошибка"))
try:
sum_report(failing)
except OSError:
print("Ошибка источника не потеряна")
Сумма: 5 Вызов источника проверен Ошибка источника не потеряна
Примеры в документации тоже можно проверить
doctest ищет примеры, оформленные как интерактивный Python-сеанс, в строках документации. Это удобно для короткой функции с устойчивым результатом. Такой пример объясняет использование, но не заменяет набор сценариев unittest: объёмная настройка, временные файлы и ветвление ошибок неудобны внутри docstring. Не используйте случайные числа, адреса объектов и текущую дату в точном ожидаемом выводе. Если порядок данных не имеет смысла, сначала нормализуйте его, а не заставляйте читателя угадывать удачный порядок запуска.
import doctest
def title(text):
"""Очистить учебный заголовок.
>>> title(" Отчёт ")
'Отчёт'
>>> title(" ")
'Без названия'
"""
return text.strip() or "Без названия"
result = doctest.testmod()
print("Примеров:", result.attempted)
print("Ошибок:", result.failed)
Примеров: 2 Ошибок: 0
Читать traceback и останавливаться в нужном месте
Traceback показывает цепочку вызовов и место исключения. Начните с типа ошибки и последнего участка вашего кода, затем проверьте входные значения. breakpoint() включает отладчик согласно настройкам окружения; в обычном случае это pdb. Команды step, next и continue помогают пройти вычисление по шагам. Не оставляйте интерактивную остановку в пакетной задаче: программа будет ждать человека. Для автоматического отчёта об исключении можно использовать logging.exception внутри обработчика и сохранить traceback без ручного копирования.
pprint помогает рассмотреть вложенные значения, а inspect — сигнатуру и устройство объекта. Используйте их для уточнения гипотезы: какой тип пришёл, на каком шаге список изменился, где источник вернул пустое значение. Бессистемные print по всему проекту дают шум и затрудняют повторение ошибки. После исправления уберите временную диагностику или превратите нужные сообщения в управляемый лог. Проверяемый результат должен оставаться доступен без включённого отладчика и без анализа внутренних переменных.
Отдельный канал для состояния программы
Лог рассказывает, что делала программа, а её основной вывод содержит результат для пользователя или следующего инструмента. Уровни DEBUG, INFO, WARNING, ERROR и CRITICAL помогают управлять подробностью. Библиотека обычно получает именованный logger и не перенастраивает корневой журнал приложения при импорте. В примере обработчик явно отправляет сообщения в stdout только ради воспроизводимой демонстрации. В обычном CLI диагностику удобно направить в stderr, чтобы перенаправление stdout сохраняло чистый JSON или CSV без служебных строк.
import logging
import sys
logger = logging.getLogger("manual.quality")
logger.handlers.clear()
logger.propagate = False
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.debug("Подробность для отладки")
logger.info("Обработано строк: %d", 3)
logger.warning("Пустая запись пропущена")
INFO: Обработано строк: 3 WARNING: Пустая запись пропущена
Не записывайте в лог настоящие пароли, токены и полные входные документы без необходимости. Для диагностики часто достаточно номера учебной записи, типа ошибки и количества обработанных строк. Вызов с шаблоном logger.info("Строк: %d", count) передаёт значение отдельно и позволяет библиотеке отложить форматирование. Выберите политику для повторяющихся ошибок: тысячи одинаковых сообщений способны скрыть первую полезную причину. Логирование не должно заменять исключение там, где задача после ошибки уже не может вернуть корректный результат.
Предупреждения, docstring и ясное имя
Предупреждение через warnings.warn сообщает о важном, но не обязательно аварийном условии: например, устаревшем API. Фильтр решает, показать, игнорировать или превратить его в исключение; поэтому не используйте warnings для проверки обязательного поля. PEP 8 предлагает договорённости о стиле, а docstring объясняет назначение, аргументы, результат и существенные ограничения функции. Название parse_quantity полезнее do_stuff. Короткие функции и разделение расчёта с эффектами помогают читателю, но искусственное дробление одного простого действия на десяток слоёв мешает пониманию.
Практика: пустая выборка — отдельный сценарий
Функция среднего значения должна отвергать пустую выборку понятным исключением. Напишите два теста: для [2, 4] ожидается 3, для пустого списка — ValueError. Не подставляйте ноль вместо ошибки молча: это изменяет смысл результата. После этого измените первый набор на один элемент и добавьте отдельный тест, чтобы убедиться в выбранном контракте функции.
Посмотреть решение
import io
import unittest
def average(values):
if not values:
raise ValueError("Пустая выборка")
return sum(values) / len(values)
class AverageTests(unittest.TestCase):
def test_values(self):
self.assertEqual(average([2, 4]), 3)
def test_empty(self):
with self.assertRaises(ValueError):
average([])
def test_single(self):
self.assertEqual(average([5]), 5)
result = unittest.TextTestRunner(stream=io.StringIO()).run(unittest.defaultTestLoader.loadTestsFromTestCase(AverageTests))
print("Тестов:", result.testsRun)
print("Успешно:", result.wasSuccessful())
Тестов: 3 Успешно: True
Первичные источники главы:
20. Командная строка и автоматизация
Параметры лучше редактирования исходника
CLI — программа, которой передают параметры из терминала или другой программы. Хороший инструмент сообщает, что принимает, куда сохраняет результат и как проверить действие до его выполнения. В примере argparse превращает строки аргументов в проверенные значения. Для воспроизводимого запуска передаётся конкретный список; в настоящей точке входа parse_args() без списка читает аргументы процесса. Поэтому справка, типы и значения по умолчанию должны описывать пользовательское действие, а не внутренние переменные реализации.
import argparse
def positive(text):
value = int(text)
if value <= 0:
raise argparse.ArgumentTypeError("нужно число больше нуля")
return value
parser = argparse.ArgumentParser(prog="report", description="Отчёт по учебной таблице")
parser.add_argument("input", help="входной CSV-файл")
parser.add_argument("--limit", type=positive, default=10, help="максимум строк")
parser.add_argument("--dry-run", action="store_true", help="показать план без записи")
args = parser.parse_args(["sales.csv", "--limit", "2", "--dry-run"])
print("Вход:", args.input)
print("Лимит:", args.limit)
print("Без записи:", args.dry_run)
Вход: sales.csv Лимит: 2 Без записи: True
Строки sys.argv ещё не являются проверенными числами или путями. Позиционные аргументы подходят для обязательных входов, именованные опции — для настроек. Значение по умолчанию должно быть безопасным и понятным. Когда у инструмента несколько разных действий, подкоманды вроде inspect и convert читаются лучше многочисленных взаимоисключающих флагов. Проверяйте аргументы до тяжёлой работы и изменения файлов; отдельное сообщение об ошибочном лимите полезнее исключения из середины обработки.
Справка должна работать без входного файла и подключения к сети. Человек сначала знакомится с командой, затем выбирает действие. Если даже --help запускает импорт данных, вынесите побочные действия из верхнего уровня в функцию выполнения после разбора параметров.
Результат в stdout, диагностика в stderr
Стандартный вывод stdout позволяет передать результат следующей программе или сохранить его в файл. Стандартный поток ошибок stderr предназначен для диагностики. Если CLI обещает JSON, строка «начинаю обработку» в stdout ломает этот контракт. Завершение с кодом 0 означает успех; ненулевой код сообщает о неуспехе, а конкретные значения документирует инструмент. Не возвращайте 0 после ошибки записи только потому, что исключение перехвачено: планировщик тогда сочтёт задачу выполненной.
Практичная структура — функция main(argv=None), возвращающая код, и raise SystemExit(main()) под защитой __main__. Внутренние функции возвращают данные или вызывают осмысленные исключения; только внешний слой переводит их в сообщение и код завершения. Так расчёт можно тестировать без завершения всего тестового процесса. Справка --help обычно заканчивает запуск успешно; ошибка разбора аргументов у argparse обычно использует код 2. В демонстрации ниже считываются только заранее подготовленные учебные данные.
import argparse
import csv
import json
from pathlib import Path
def main(argv=None):
parser = argparse.ArgumentParser(prog="csv-json")
parser.add_argument("input", nargs="?", default="sales.csv")
args = parser.parse_args(argv)
with Path(args.input).open(encoding="utf-8", newline="") as stream:
reader = csv.DictReader(stream)
if reader.fieldnames != ["name", "quantity"]:
raise ValueError("Ожидаются колонки name,quantity")
rows = []
for row in reader:
if set(row) != {"name", "quantity"} or row["name"] is None or row["quantity"] is None:
raise ValueError("Неверное число полей")
value = int(row["quantity"])
if not row["name"].strip() or value < 0:
raise ValueError("Некорректная строка")
rows.append({"name": row["name"].strip(), "quantity": value})
print(json.dumps(rows, ensure_ascii=False, sort_keys=True))
return 0
if __name__ == "__main__":
import sys
try:
status = main()
except (OSError, ValueError) as error:
print("Ошибка:", error, file=sys.stderr)
status = 2
raise SystemExit(status)
[{"name": "Кабель", "quantity": 3}]
Объясните рабочую папку и выходной файл
Относительный входной путь считается от рабочей папки процесса, а не обязательно от расположения скрипта. В справке укажите это правило и разрешите явный путь. Для записи результата заранее решите, допустима ли перезапись, требуется ли создание каталогов и как сообщить об уже занятом имени. Не считайте пользовательский путь безопасным только потому, что он прошёл через Path. Если инструмент ограничен рабочей папкой, проверяйте границу отдельно и учитывайте ссылки. Полезный dry-run показывает конкретные выбранные файлы и предполагаемые изменения.
Предварительный план должен совпадать с выполнением
Режим --dry-run рассчитывает тот же план, который использует настоящее выполнение, но не применяет изменения. Это помогает проверить имя выходного файла, число записей и порядок. Не делайте отдельный приблизительный алгоритм «для красивого превью»: он быстро начнёт отличаться от реального действия. В примере план строится один раз, сортируется, затем передаётся в ограниченное выполнение. Учебная папка создаётся внутри временного каталога и удаляется после опыта; файлы пользователя не затрагиваются.
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
root = Path(folder)
for name in ["b.txt", "a.txt"]:
(root / name).write_text("demo", encoding="utf-8")
plan = [(path, path.with_suffix(".md")) for path in sorted(root.glob("*.txt"))]
for source, target in plan:
print("План:", source.name, "->", target.name)
print("После dry-run:", len(list(root.glob("*.txt"))), "TXT")
for source, target in plan:
if target.exists():
raise FileExistsError(target.name)
source.rename(target)
print("После выполнения:", len(list(root.glob("*.md"))), "MD")
План: a.txt -> a.md План: b.txt -> b.md После dry-run: 2 TXT После выполнения: 2 MD
Передавайте дочернему процессу список аргументов
subprocess.run подходит для запуска локальной вспомогательной команды. Передавайте аргументы списком, сохраняйте shell=False, задавайте таймаут и решайте, нужен ли check=True. Для запуска того же Python используйте sys.executable: команда python из PATH может выбрать другую среду. Пробелы и кавычки внутри одного аргумента не требуют ручной сборки shell-строки. При check возникает CalledProcessError для ненулевого завершения; превышение таймаута вызывает TimeoutExpired. Не путайте эти случаи с отсутствием самого исполняемого файла.
import subprocess
import sys
process = subprocess.run(
[sys.executable, "-c", "import sys; print(sys.argv[1])", "аргумент с пробелами"],
shell=False, check=True, capture_output=True,
text=True, encoding="utf-8", timeout=5,
)
print("Код:", process.returncode)
print(process.stdout, end="")
Код: 0 аргумент с пробелами
Shell-команды Windows и POSIX имеют разные правила экранирования. Модуль shlex ориентирован на Unix-подобные оболочки и не является универсальным Windows-парсером. Нужные операции с файлами проще выполнить через pathlib или shutil, чем запускать встроенную shell-команду. При необходимости дочернему процессу передают выбранный cwd и окружение; не выводите весь словарь окружения в лог. Комбинация capture_output и огромного вывода также требует решения о памяти: для потокового журнала используйте подходящую обработку потоков.
Настройки, платформа и завершение
Выберите приоритет настроек: например, явная опция, затем переменная окружения, затем значение по умолчанию. Переменная окружения — строка, которую нужно преобразовать и проверить. Имена собственного инструмента лучше делать специфичными, не меняя PATH или HOME ради лимита отчёта. platform помогает описать среду в диагностике, но без необходимости не ветвите простой алгоритм по названию системы. При Ctrl+C корректно освободите файлы и другие ресурсы; переносимость сигналов ограничена платформой, а сигнал завершения не гарантирует выполнение любого cleanup.
Практика: понятный лимит из строки
Сделайте проверку лимита 1–100 и используйте её как тип аргумента. Проверьте текст «10», ноль, нечисловую строку и 101. В примере функция вызывает ArgumentTypeError, чтобы argparse мог оформить пользовательскую ошибку. При упаковке настоящего инструмента добавьте команду в project.scripts файла pyproject.toml; это устанавливаемая точка входа, а не команда, которую нужно вручную копировать каждому пользователю в глобальную папку.
Посмотреть решение
import argparse
def limit(text):
try:
value = int(text)
except ValueError as error:
raise argparse.ArgumentTypeError("Лимит должен быть целым") from error
if not 1 <= value <= 100:
raise argparse.ArgumentTypeError("Лимит должен быть от 1 до 100")
return value
for text in ["10", "0", "нет", "101"]:
try:
print(limit(text))
except argparse.ArgumentTypeError as error:
print(error)
10 Лимит должен быть от 1 до 100 Лимит должен быть целым Лимит должен быть от 1 до 100
21. Сеть, HTTP и взаимодействие с сервисом
Собирайте параметры средствами библиотеки
HTTP-клиенту нужны адрес, метод, заголовки и правила обработки результата. Начните с адреса: путь и query-параметры — разные компоненты URL. urllib.parse.urlencode корректно кодирует пробелы, кириллицу и специальные символы в параметрах. Не склеивайте пользовательский текст через «?q=» и амперсанды: часть строки может превратиться в другой параметр. Для повторяющегося поля используйте список значений и doseq=True. Фрагмент после решётки предназначен для клиента и обычно не передаётся серверу в HTTP-запросе.
from urllib.parse import urlencode, urlsplit, parse_qs, urljoin
query = urlencode({"q": "CSS сетка", "tag": ["python", "web"]}, doseq=True)
url = "https://example.test/search?" + query
parts = urlsplit(url)
print("Хост:", parts.hostname)
print("Запрос:", parse_qs(parts.query)["q"][0])
print("Метки:", ", ".join(parse_qs(parts.query)["tag"]))
print(urljoin("https://example.test/docs/", "guide"))
Хост: example.test Запрос: CSS сетка Метки: python, web https://example.test/docs/guide
urljoin разрешает относительный адрес, но абсолютный второй аргумент способен заменить исходный хост. Если программа допускает только определённый сервис, проверяйте схему и конечный адрес по выбранной политике, учитывая перенаправления. Парсер URL не является проверкой доступности и доверия к адресу. В этой главе example.test используется только для разбора строки; настоящие запросы направляются исключительно на собственный сервер 127.0.0.1. Внешний аккаунт, токен и установленный сетевой пакет для воспроизведения не нужны.
Для библиотеки удобно передавать настроенный клиент отдельным объектом. Глобальная установка opener меняет поведение других запросов в процессе; собственный экземпляр оставляет настройку рядом с кодом, которому она нужна, и упрощает подмену источника в тесте.
Получите ответ собственного учебного сервера
Следующий скрипт запускает сервер на свободном локальном порту, делает запрос и завершает сервер в finally. Номер порта не выводится, поэтому результат остаётся воспроизводимым. ProxyHandler({}) относится только к учебному localhost-клиенту и исключает влияние настроенного внешнего прокси. http.server удобен для такого стенда, но не заменяет производственный сервер с полноценной обработкой доступа и нагрузки. Не привязывайте учебный сервер к публичному интерфейсу без отдельной задачи: здесь он доступен только с этого компьютера.
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
from urllib.request import build_opener, ProxyHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b'{"quantity": 3}'
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
url = f"http://127.0.0.1:{server.server_port}/data"
opener = build_opener(ProxyHandler({}))
with opener.open(url, timeout=3) as response:
body = response.read(1025)
if len(body) > 1024:
raise ValueError("Ответ слишком большой")
data = json.loads(body.decode("utf-8"))
if not isinstance(data, dict) or type(data.get("quantity")) is not int:
raise ValueError("Неожиданная структура ответа")
print("Статус:", response.status)
print("Количество:", data["quantity"])
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
Статус: 200 Количество: 3
Статус 200 — ещё не правильные данные
Успешный HTTP-статус говорит о результате запроса на уровне протокола, а не о соответствии ответа вашей модели. Проверьте допустимый Content-Type, ограничьте размер, декодируйте известную кодировку и только затем разбирайте JSON. После разбора проверьте обязательные поля, типы и диапазоны. Заголовок Content-Length может отсутствовать или быть непригодным для доверенной оценки; ограничение чтения всё равно нужно. В примере читается максимум на один байт больше лимита, чтобы отличить разрешённый размер от превышения.
Ответ — ресурс, который следует закрывать через with. Таймаут urlopen относится к блокирующим операциям, например установлению соединения, и не обязательно ограничивает полное время всей задачи. При сложном клиенте отдельно задают общий дедлайн, время подключения и ожидания данных согласно возможностям библиотеки. Если ответ может сжиматься, ограничивайте и объём после распаковки. Зависание и чрезмерный ответ должны приводить к понятной ошибке, а не к бесконечному ожиданию или загрузке всей памяти.
Различайте HTTP-ошибку и ошибку JSON
HTTPError содержит код HTTP, а URLError описывает проблему доступа или транспорта. HTTPError является подклассом URLError, поэтому более конкретный обработчик ставят первым. Ошибка декодирования и JSONDecodeError относятся уже к содержимому ответа. Не объединяйте их сообщением «нет интернета»: сервер мог успешно вернуть неверные данные. При регистрации ошибки оставляйте полезную причину и идентификатор учебной операции, не печатая заголовок авторизации или полный документ пользователя.
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
from urllib.error import HTTPError
from urllib.request import build_opener, ProxyHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/missing":
self.send_error(404)
return
body = b'{broken}'
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
opener = build_opener(ProxyHandler({}))
try:
for path in ["/missing", "/bad-json"]:
try:
with opener.open(f"http://127.0.0.1:{server.server_port}{path}", timeout=3) as response:
json.loads(response.read(1024).decode("utf-8"))
except HTTPError as error:
print("HTTP:", error.code)
error.close()
except json.JSONDecodeError:
print("Данные: неверный JSON")
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
HTTP: 404 Данные: неверный JSON
Метод определяет ожидаемое действие
GET обычно запрашивает представление ресурса, POST часто создаёт или запускает действие, PUT заменяет заданное представление, DELETE удаляет ресурс по контракту сервиса. Реальный смысл определяется API. Request позволяет явно задать method и headers; при отправке JSON сформируйте байты UTF-8 и соответствующий Content-Type. Не помещайте секретный токен в исходник, адрес или пример лога. Cookie, перенаправления и авторизация требуют отдельной политики; стандартная библиотека предоставляет инструменты, но не угадывает правила конкретного сервиса.
Повтор должен быть ограниченным и осмысленным
Не повторяйте любой неудачный запрос бесконечно. Ограничьте число попыток, выберите временные ошибки и паузу; при подходящем ответе учтите Retry-After. Идемпотентность означает, что повтор не должен создавать новый дополнительный эффект. Даже если клиент не получил ответ на создание записи, сервер мог уже выполнить действие. Для такого API нужна согласованная защита от дублей, например поддерживаемый сервисом ключ идемпотентности. Ошибку схемы JSON повтор обычно не исправляет; сначала отделите сетевую попытку от преобразования данных.
from urllib.error import URLError
attempts = 0
def read_demo():
global attempts
attempts += 1
if attempts < 3:
raise URLError("временный учебный сбой")
return {"count": 4}
def retry_read(operation, limit):
for number in range(1, limit + 1):
try:
return operation()
except URLError:
if number == limit:
raise
raise ValueError("Нужна хотя бы одна попытка")
data = retry_read(read_demo, 3)
print("Попыток:", attempts)
print("Получено:", data["count"])
Попыток: 3 Получено: 4
HTTP-клиент, TLS и сокет — разные уровни
socket работает с низкоуровневым обменом байтами: одно чтение не обязано вернуть целое сообщение. Для HTTP используйте клиент, который понимает протокол, вместо ручной склейки сетевых строк. HTTPS добавляет TLS; стандартный безопасный контекст проверяет сертификат и имя хоста. Не отключайте эту проверку как обычное исправление подключения: сначала проверьте адрес, время системы и доверенные сертификаты. Сторонний клиент или веб-фреймворк выбирают по требованиям задачи, явно отделяя его от стандартной библиотеки.
Практика: проверка ответа без настоящей сети
Выделите преобразование ответа в отдельную функцию. Пусть она принимает байты с JSON и возвращает неотрицательное целое count. Проверьте обычный ответ, логическое значение и повреждённый JSON. Это тест границы данных, поэтому запуск сервера для него лишний. После такой проверки настоящий HTTP-слой может использовать ту же функцию, сохраняя разные сообщения о транспортной ошибке и неправильном содержимом.
Посмотреть решение
import json
def parse_response(body):
value = json.loads(body.decode("utf-8"))
if not isinstance(value, dict) or type(value.get("count")) is not int or value["count"] < 0:
raise ValueError("Нужно неотрицательное целое count")
return value["count"]
for body in [b'{"count": 2}', b'{"count": true}', b'not-json']:
try:
print(parse_response(body))
except (ValueError, UnicodeDecodeError):
print("Неверные данные")
2 Неверные данные Неверные данные
Первичные источники главы:
22. Asyncio, потоки, процессы и GIL
Конкурентность не всегда означает параллельный расчёт
Конкурентная программа ведёт несколько работ в перекрывающиеся промежутки времени. Параллельная действительно выполняет работы одновременно. Если большая часть задачи — ожидание файла или сервиса, перекрытие ожиданий уже полезно. Если задача непрерывно считает Python-выражения, нужна модель, которая действительно позволяет использовать несколько вычислительных ресурсов. Сначала измерьте последовательный вариант: организация задач, передача данных и запуск процессов тоже имеют стоимость. Несколько работников способны замедлить маленький расчёт или перегрузить общий источник данных.
| Задача | Начальный выбор | Что проверить |
|---|---|---|
| Блокирующее ожидание | Потоки | Число работников и общие данные |
| Асинхронный ввод-вывод | Asyncio | Все ли ожидания освобождают цикл |
| Тяжёлый Python-расчёт | Процессы или подходящий отдельный интерпретатор | Стоимость передачи и совместимость |
Async def создаёт корутину, await выполняет ожидание
Вызов async-функции возвращает объект корутины. Для её выполнения нужен await либо создание задачи в работающем цикле. Несколько последовательных await не превращаются автоматически в конкурентный запуск. В примере задачи создаются вместе внутри TaskGroup, доступного с Python 3.11. Список задач сохраняет выбранный порядок результатов; порядок фактического завершения может быть другим. Небольшое asyncio.sleep лишь моделирует освобождающее цикл ожидание, а не ускоряет сам расчёт квадратов.
import asyncio
async def calculate(value):
await asyncio.sleep(0)
return value * value
async def main():
async with asyncio.TaskGroup() as group:
tasks = [group.create_task(calculate(value)) for value in [3, 1, 2]]
print("Результаты:", [task.result() for task in tasks])
asyncio.run(main())
Результаты: [9, 1, 4]
Обычный time.sleep внутри корутины блокирует её поток вместе с циклом событий. То же происходит при длительном синхронном чтении или вычислении без подходящего освобождения управления. Для блокирующего I/O можно использовать asyncio.to_thread, помня о природе библиотеки и отмене. Асинхронный клиент должен поддерживать эту модель сам. async with и async for выражают асинхронное управление ресурсом и итерацию; добавление ключевого слова к обычному объекту не делает его автоматически асинхронным.
Отмена должна пройти через cleanup
TaskGroup связывает время жизни созданных задач: выход ожидает их, а существенная ошибка приводит к отмене соседних задач и обработке группы исключений. Отмена доставляется в точках ожидания как CancelledError; освобождайте ресурс в finally и обычно пропускайте отмену дальше. Если поймать её и продолжить бесконечную работу, таймаут и группа перестанут вести себя ожидаемо. Таймаут ограничивает ожидание по модели asyncio, но не способен насильно остановить произвольный блокирующий код, который не возвращает управление.
import asyncio
async def worker(started, cleaned):
started.set()
try:
await asyncio.Event().wait()
finally:
cleaned.set()
async def main():
started, cleaned = asyncio.Event(), asyncio.Event()
task = asyncio.create_task(worker(started, cleaned))
await started.wait()
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Отмена получена")
print("Ресурс освобождён:", cleaned.is_set())
asyncio.run(main())
Отмена получена Ресурс освобождён: True
Потоки: передавайте результаты через очередь
Потоки работают внутри одного процесса и разделяют его память. Это удобно для ожидания, но создаёт гонки: два участника могут изменить одно состояние в неподходящий момент. queue.Queue обеспечивает безопасный канал передачи между потоками. В примере каждый работник отдаёт готовую пару, главный поток ждёт завершения через join и сортирует результат. Размер очереди и число работников нужно ограничивать для большого потока задач; иначе скорость поступления работы может оказаться выше скорости обработки.
from queue import Queue
from threading import Thread, Lock
results = Queue()
lock = Lock()
completed = 0
def work(value):
global completed
results.put((value, value * value))
with lock:
completed += 1
threads = [Thread(target=work, args=(value,)) for value in [3, 1, 2]]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
collected = [results.get() for _ in threads]
print("Результаты:", sorted(collected))
print("Завершено:", completed)
Результаты: [(1, 1), (2, 4), (3, 9)] Завершено: 3
Lock защищает выбранную критическую секцию, а не весь смысл программы сам по себе. Договоритесь, какие данные он охраняет и в каком порядке берутся несколько блокировок. Не удерживайте блокировку во время долгого сетевого запроса, если это не требуется контрактом: остальные работники будут ждать. GIL не заменяет такую защиту составного действия. Ошибка внутри обычного потока также не обязана автоматически подняться в главном: передавайте её через явный канал либо используйте Future с вызовом result.
Для процессов нужен импортируемый код
Процессы имеют отдельную память и передают сериализуемые данные. Для функций пула используйте определения уровня модуля, а не локальную функцию или lambda. Защитите запуск через __main__. В примере явно выбран spawn и функция находится в соседнем модуле; это показывает переносимое устройство, а не предполагает старое наследование состояния через fork. На Windows и macOS обычно используется spawn; с Python 3.14 стандартный выбор POSIX изменился на forkserver там, где он поддерживается.
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import get_context
from manual_workers import square
def main():
with ProcessPoolExecutor(max_workers=2, mp_context=get_context("spawn")) as pool:
values = list(pool.map(square, [1, 2, 3, 4]))
print("Квадраты:", values)
if __name__ == "__main__":
main()
Квадраты: [1, 4, 9, 16]
Не передавайте в процесс открытое соединение, заблокированный ресурс или огромный объект без оценки стоимости. Разделите работу на разумные пачки и выбирайте число работников по измерению, а не по максимальному доступному числу ядер. Executor.map отдаёт результаты по порядку входа; as_completed позволяет обработать завершившиеся раньше. Ошибка проявится при получении соответствующего результата. Запрашивая отмену Future, не рассчитывайте, что уже начавшаяся операция немедленно прервётся: это зависит от её состояния и модели.
Обычная и free-threaded сборки различаются
В обычной сборке CPython GIL ограничивает одновременное исполнение Python-кода потоками одного интерпретатора, но потоки по-прежнему полезны для I/O; некоторые расширения освобождают GIL при вычислении. Free-threaded сборка появилась в Python 3.13 и получила официальный статус поддержки в 3.14. Наличие такой сборки и фактически выключенный GIL — разные факты: несовместимое расширение может включить его обратно. Проверки sysconfig.get_config_var("Py_GIL_DISABLED") и sys._is_gil_enabled() отвечают на разные вопросы.
Изолированные интерпретаторы и контекст задач
InterpreterPoolExecutor и concurrent.interpreters появились в Python 3.14. Они предлагают отдельные интерпретаторы с изолированным состоянием; это не обычные потоки с общей копией всех изменяемых объектов. Учитывайте передачу данных, импорты и совместимость расширений. contextvars помогает хранить контекст логической задачи, например учебный идентификатор операции, не смешивая его между корутинами. Это средство передачи контекста, а не блокировка общей базы. Выбирайте новый механизм по реальной пользе и измерению, сохраняя более простой путь там, где он достаточен.
Практика: результат без гонки вывода
Запустите три функции через ThreadPoolExecutor, верните значения и напечатайте итог только из главного потока. Не проверяйте порядок промежуточных print работников: планировщик не обещает такого порядка. Затем замените одну функцию на вызывающую ValueError и убедитесь, что result или обход map передаёт ошибку главному коду. Это практичная граница между параллельным выполнением и последовательным оформлением отчёта.
Посмотреть решение
from concurrent.futures import ThreadPoolExecutor
def prepare(value):
return value.upper()
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(prepare, ["one", "two", "three"]))
print(", ".join(results))
ONE, TWO, THREE
23. Производительность и память: сначала измерить задачу
Сформулируйте наблюдаемое затруднение
«Программа медленная» слишком широко для полезного измерения. Укажите действие: загрузка файла, поиск записей, расчёт отчёта или первый импорт. Затем определите данные, ожидаемый результат и условия запуска. Так можно повторить сценарий после изменения и сравнить одно и то же поведение.
Отделяйте ожидание ввода-вывода от вычислений. Длительная операция может ждать диск, сеть или блокировку; ускорение арифметики не устранит эту задержку. Время всей задачи и время CPU отвечают разным вопросам. Для собственного короткого участка пригодится perf_counter, а для структуры вызовов — профилирование.
Сохраните исходный корректный результат. Оптимизация, которая пропускает часть данных или меняет округление, должна оцениваться как новое поведение. Проверьте обычный набор, пустые данные и достаточно большой вход. Изменяйте одну основную причину за раз, чтобы было понятно, какой шаг повлиял на измерение.
Структура данных определяет объём работы
Повторный поиск среди множества элементов может тратить время на последовательный обход. Если задача требует только проверки наличия, set способен изменить характер работы. Но его создание тоже занимает время и память, порядок значений иной, а элементы должны быть хешируемыми.
Сначала рассмотрите весь сценарий. Для одного поиска в маленьком списке подготовка дополнительного множества может быть лишней. Для тысяч повторных проверок индекс создаётся один раз и используется многократно. Сравнение должно учитывать подготовку, если она входит в реальное действие.
Похожая логика касается сортировки, группировки и повторного преобразования. Не выполняйте сортировку внутри каждого поиска, если порядок уже можно подготовить заранее. Кэш может сохранить вычисление, а генератор — убрать лишнее накопление. Каждое решение меняет ограничения, поэтому вместе со временем проверьте допустимые данные и расход памяти.
Короткое повторяемое измерение
Timeit выполняет небольшой участок много раз, уменьшая влияние случайной длительности одного запуска. Repeat возвращает несколько замеров, а number задаёт число выполнений внутри каждого. Разделите подготовку данных и измеряемое действие согласно своему вопросу: хотите измерить только поиск или всю обработку вместе с созданием индекса?
Следующий пример выполняет реальное измерение, но для автоматической проверки выводит только его устойчивые признаки. Значения samples и их минимум зависят от компьютера, фоновой нагрузки и версии Python. Для исследования на своём устройстве добавьте print(samples) и сравните одинаковое число запусков нескольких вариантов.
Не заявляйте преимущество по одному случайному числу. Сравните несколько повторов, объясните размер входа и версию. Учтите также поведение GC: timeit по умолчанию временно отключает сборку мусора в своём измерении. Если именно её стоимость существенна для задачи, настройте сценарий осмысленно.
from timeit import Timer
source = list(range(5000))
index = set(source)
samples = Timer(lambda: 4999 in index).repeat(repeat=3, number=1000)
print(len(samples))
print(all(value >= 0 for value in samples))
print((4999 in source) == (4999 in index))
3 True True
Профиль показывает распределение работы
В Python 3.15 профилировщики объединены под именем profiling. Profiling.tracing записывает вызовы и затраченное время, а profiling.sampling получает статистические снимки выполнения. Прежний cProfile остаётся совместимым именем детерминированного профилировщика. Ссылки этой главы ведут к актуальному описанию, а не к старому отдельному адресу документации.
Детерминированный профиль помогает найти функцию, вызываемую неожиданно часто, или дорогой участок большого маршрута. Собственное время отличается от накопленного: второе включает вложенные вызовы. Откройте таблицу с сортировкой по нужному показателю и сопоставьте её с предполагаемой причиной задержки.
Статистическое профилирование даёт другой вид наблюдения и зависит от режима и частоты выборок. Даже когда инструмент описывает низкие накладные расходы, не переносите результат одной программы на все системы. Для первого опыта профилируйте собственный короткий скрипт и сравнивайте исправление на том же наборе данных.
from profiling.tracing import Profile
from pstats import Stats
from io import StringIO
def total(values):
return sum(value * value for value in values)
profile = Profile()
result = profile.runcall(total, range(5))
stats = Stats(profile, stream=StringIO())
print(result)
print(any(key[2] == "total" for key in stats.stats))
30 True
Что именно измеряет память
Tracemalloc отслеживает выделения памяти, связанные с Python, и помогает сравнить снимки или текущее значение с пиком. Это не полное измерение всей памяти процесса, включая каждый нативный компонент. Для изображения, библиотеки с собственным буфером или ресурса ОС могут понадобиться дополнительные инструменты.
Запускайте наблюдение до исследуемых выделений. Сначала получите базовый снимок, затем повторяемое действие, после чего сравните места прироста. Сохранённые объекты могут удерживаться кэшем, списком, обработчиком или замыканием. Само слово del удаляет конкретную ссылку; оно не обещает немедленно вернуть всю память операционной системе.
Пример ниже показывает текущие выделения, пик и освобождение собственного списка. Вывод содержит отношения величин, а не нестабильные числа байтов. Для своей задачи изучайте конкретные строки и удерживающие ссылки. Вызов gc.collect не должен становиться обычной заменой поиска причины роста памяти.
import gc
import tracemalloc
tracemalloc.start()
values = [str(number) for number in range(1000)]
current, peak = tracemalloc.get_traced_memory()
print(len(values))
print(peak >= current > 0)
del values
gc.collect()
after, _ = tracemalloc.get_traced_memory()
print(after < current)
tracemalloc.stop()
1000 True True
Кэш, ленивость и особенности интерпретатора
Кэш полезен, когда одинаковые аргументы должны давать одинаковый результат и повторный расчёт дорог. Если данные меняются снаружи, продумайте срок хранения или ключ версии. Ограничение maxsize и очистка позволяют управлять удержанием объектов, но требуют понимания жизненного цикла программы.
Генераторы подходят для последовательной обработки, когда не требуется сохранить весь набор. Они уменьшают промежуточное накопление, но не отменяют стоимость каждого вычисления. Для сортировки или повторного произвольного доступа список всё равно может понадобиться. Проверьте, что ускорение не связано с тем, что ленивый результат ещё никто не потребил.
Возможности CPython меняются по версиям и сборкам. Экспериментальный JIT в 3.15 улучшен, но не является обещанием ускорить любую задачу. Обычная сборка с GIL и free-threaded сборка также имеют разные условия. Указывайте фактический интерпретатор, настройки и совместимость зависимостей, когда сравниваете такой вариант.
from functools import lru_cache
calls = []
@lru_cache(maxsize=2)
def cost(number):
calls.append(number)
return number * number
print([cost(2), cost(2), cost(3)])
print(calls)
print(cost.cache_info().currsize)
cost.cache_clear()
print(cost.cache_info().currsize)
[4, 4, 9] [2, 3] 2 0
Попробуйте самостоятельно. Индекс из set ускорил поиск. Как понять, стоит ли добавлять его для одного короткого списка?
Посмотреть решение
Измерьте весь пользовательский сценарий, включая создание множества, если оно создаётся при каждом действии. Сравните несколько размеров входа и число повторных поисков. Затем проверьте память, порядок и требования к хешируемости. Запишите условия, при которых изменение полезно.
Первичные источники главы:
24. Законченные проекты и перенос на свой компьютер
Законченный проект начинается с проверяемого результата
Небольшая законченная программа отличается от набора удачных строк кода понятным входом, результатом и способом повторения. Для отчёта сформулируйте: какие колонки принимаются, что считается, где сохраняется результат и какие ошибки прекращают обработку. Сразу подготовьте учебные данные, которые можно распространять без реальных записей клиента. Читатель должен запустить пример и узнать, что получилось правильно, не подбирая неизвестный файл и не догадавшись о скрытом параметре. Для первого проекта стандартной библиотеки достаточно: усложнение окружения пока не помогает понять сам расчёт.
Выберите одну полезную задачу и доведите её до конца. Например, объедините одинаковые позиции таблицы, выведите проверенные итоги и сохраните отчёт. Не начинайте одновременно строить веб-интерфейс, авторизацию, фоновые задачи и универсальный импорт любого формата. Граница проекта может быть небольшой и честной: UTF-8 CSV с известными колонками, целое неотрицательное количество, один локальный запуск. Когда такие ограничения записаны, проверить программу легче, а пользователь понимает, какие данные ему нужно подготовить. Расширение формата станет отдельным изменением с собственными примерами.
Проект 1: CSV → JSON → SQLite
Следующий проект читает полностью предоставленный учебный sales.csv, объединяет количество по названию и сохраняет JSON в собственной временной папке. SQLite используется в памяти: реальные базы не открываются. Отдельная проверка сравнивает JSON и результат SQL-выборки. В таком отчёте количество — целое число; для денежных сумм потребовалось бы другое явно выбранное представление. При первом неправильном значении импорт прекращается до сохранения результата, поэтому частичный отчёт не будет выглядеть завершённым.
import csv
import json
import sqlite3
from pathlib import Path
from tempfile import TemporaryDirectory
def load_totals(path):
totals = {}
with path.open(encoding="utf-8", newline="") as stream:
reader = csv.DictReader(stream)
if reader.fieldnames != ["name", "quantity"]:
raise ValueError("Нужны колонки name,quantity")
for number, row in enumerate(reader, start=2):
if set(row) != {"name", "quantity"} or row["name"] is None or row["quantity"] is None:
raise ValueError(f"Неверное число полей в строке {number}")
name = row["name"].strip()
try:
quantity = int(row["quantity"])
except ValueError as error:
raise ValueError(f"Количество в строке {number} должно быть целым") from error
if not name or quantity < 0:
raise ValueError(f"Некорректная строка {number}")
totals[name] = totals.get(name, 0) + quantity
return [{"name": name, "quantity": totals[name]} for name in sorted(totals)]
records = load_totals(Path("sales.csv"))
with TemporaryDirectory() as folder:
path = Path(folder) / "report.json"
path.write_text(json.dumps(records, ensure_ascii=False), encoding="utf-8")
connection = sqlite3.connect(":memory:", autocommit=False)
try:
with connection:
connection.execute("CREATE TABLE totals (name TEXT PRIMARY KEY, quantity INTEGER NOT NULL)")
connection.executemany("INSERT INTO totals VALUES (?, ?)", [(row["name"], row["quantity"]) for row in records])
fetched = [{"name": name, "quantity": quantity} for name, quantity in connection.execute("SELECT name,quantity FROM totals ORDER BY name")]
print("Позиций:", len(fetched))
print("Всего единиц:", sum(row["quantity"] for row in fetched))
print("JSON и SQLite согласованы:", json.loads(path.read_text(encoding="utf-8")) == fetched)
for row in fetched:
print(row["name"], row["quantity"])
finally:
connection.close()
Позиций: 2 Всего единиц: 9 JSON и SQLite согласованы: True Кабель 5 Клемма 4
Проверьте четыре сценария: обычную таблицу, только строку заголовков, отрицательное количество и неверные колонки. Для пустой таблицы итоговые количества равны нулю, а список позиций пуст. Если задача требует собирать все ошибки строк, добавьте отдельный список диагностики и условие публикации результата; не проглатывайте ошибки без отметки. Преобразование названий также должно быть осознанным: strip удаляет крайние пробелы, но не объединяет разные регистры и варианты написания. Решение о такой нормализации относится к предметной задаче, а не к JSON или SQL.
Для постоянной CLI-базы вместо :memory: понадобится явно выбранный путь. Продумайте повторный запуск: повторная вставка уже импортированного файла не должна удвоить итог случайно. Можно хранить идентификатор партии и уникальное ограничение либо полностью заменять одну известную учебную выборку в транзакции. После успешного импорта команда inspect покажет число строк и итог, а export сохранит JSON. Разделение этих команд позволяет человеку сначала осмотреть состояние и только затем запустить действие. Наличие SQLite само по себе не определяет такую политику.
Проект 2: список файлов с проверяемыми характеристиками
Второй проект составляет отчёт о собственной учебной папке. Он сохраняет относительное имя, размер в байтах и SHA-256 каждого TXT-файла. Это позволяет сравнить исходники при переносе и заметить изменение содержимого. Хеш не доказывает авторство и безопасность файла; он подтверждает совпадение байтов при выбранном алгоритме. Отчёт записывается вне входной папки, чтобы следующий запуск не включил его в собственный вход. Для большого набора данных чтение хеша лучше выполнять по блокам, не загружая каждый файл целиком в память.
import hashlib
import json
from pathlib import Path
from tempfile import TemporaryDirectory
def digest(path):
value = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(8192), b""):
value.update(block)
return value.hexdigest()
with TemporaryDirectory() as folder:
root = Path(folder)
source = root / "input"
source.mkdir()
(source / "a.txt").write_text("abc", encoding="utf-8")
(source / "b.txt").write_text("Привет", encoding="utf-8")
(source / "notes.md").write_text("не входит", encoding="utf-8")
records = []
for path in sorted(source.glob("*.txt")):
if path.is_file() and not path.is_symlink():
records.append({"path": path.relative_to(source).as_posix(), "bytes": path.stat().st_size, "sha256": digest(path)})
report = root / "manifest.json"
report.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding="utf-8")
print("Файлов:", len(records))
print("Байтов:", sum(record["bytes"] for record in records))
print("Длина всех хешей 64:", all(len(record["sha256"]) == 64 for record in records))
print("Имена:", ", ".join(record["path"] for record in records))
Файлов: 2 Байтов: 15 Длина всех хешей 64: True Имена: a.txt, b.txt
Перед обработкой реальной выбранной папки покажите план: область поиска, маску и число файлов. Отдельно решите, разрешены ли ссылки и рекурсивный обход. В примере поиск плоский и ссылки пропускаются. При проверке недоверенного каталога могут требоваться более строгие ограничения и учёт изменения файлов во время чтения: размер из stat и содержимое для хеша получены не одновременно. Если файл исчез, сообщение должно назвать конкретную операцию. Не превращайте ошибку доступа в размер ноль, иначе отчёт будет уверенно описывать то, что не удалось проверить.
Проект 3: исходники, README и тест в одном архиве
Передайте человеку не только запускаемый файл, но и краткую инструкцию, учебные данные и способ проверки. В примере архив содержит модуль расчёта, точку входа пакета, минимальные метаданные, README, данные и тест. Скрипт создаёт ZIP во временной папке и проверяет CRC без распаковки. Файлы перечислены явно: случайные документы рабочей папки в архив не попадут. Это архив исходного проекта, а не автоматически собранный wheel. Установка дистрибутива и упаковка исходников — разные результаты, которые нужно называть точно.
import io
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile, ZIP_DEFLATED
from manual_project.core import total
paths = ["manual_project/__init__.py", "manual_project/core.py", "manual_project/__main__.py", "tests/test_core.py", "README.md", "pyproject.toml", "data/example.csv"]
suite = unittest.defaultTestLoader.discover("tests")
result = unittest.TextTestRunner(stream=io.StringIO()).run(suite)
if not result.wasSuccessful():
raise RuntimeError("Тесты не прошли")
with TemporaryDirectory() as folder:
archive_path = Path(folder) / "manual-project.zip"
with ZipFile(archive_path, "w", compression=ZIP_DEFLATED) as archive:
for relative in paths:
archive.write(Path(relative), arcname=relative)
with ZipFile(archive_path) as archive:
print("Записей:", len(archive.namelist()))
print("CRC корректен:", archive.testzip() is None)
print("Тестов:", result.testsRun)
print("Итого:", total([2, 3]))
Записей: 7 CRC корректен: True Тестов: 4 Итого: 5
README отвечает на вопросы до первого запуска: версия Python, команда, рабочая папка, входной формат, ожидаемый результат, расположение файлов и способ отмены ошибочного действия. Если скрипт создаёт временные результаты и автоматически удаляет их, напишите это отдельно; если сохраняет постоянный файл — назовите его. Добавьте один известный успешный пример и одну типичную ошибку с решением. Пользователю удобнее получить небольшой проверяемый сценарий, чем перечень всех возможных параметров без порядка действий. Лицензию и происхождение сторонних материалов указывают вместе с проектом.
Воспроизведите запуск в новом окружении
Проверка на машине автора недостаточна: нужный пакет мог давно стоять глобально, а правильная папка была открыта случайно. Создайте новое виртуальное окружение и запустите команды явным интерпретатором. Для наших примеров внешние зависимости не нужны. Если проект имеет зависимости, установите их согласно описанию и сохранённым версиям, а не по случайному набору из личной среды. Команды ниже — инструкция терминала для распакованного проекта; создание окружения и установка не входят в самостоятельно выполняемые примеры главы.
python -m venv .venv
# Windows, без активации:
.venv\Scripts\python.exe -m manual_project
.venv\Scripts\python.exe -m unittest discover -s tests
# macOS/Linux, без активации:
.venv/bin/python -m manual_project
.venv/bin/python -m unittest discover -s tests
Для настоящего устанавливаемого пакета pyproject.toml должен соответствовать выбранному сборщику и структуре файлов. Получение wheel обычно требует инструмента сборки и его зависимостей; setuptools в нашем описании — сторонний сборщик, не часть стандартной библиотеки. Не утверждайте, что архив уже прошёл сборку, если вы лишь добавили такой файл. Сначала проверьте построенный дистрибутив и установку в чистую среду. Если пакет предоставляет CLI, добавьте осмысленную точку входа project.scripts и проверку --help без побочных действий.
Добавляйте HTTP и async, когда задача это требует
Локальный HTTP-стенд из главы о сети можно связать с отчётом: обработчик возвращает заранее проверенный JSON, клиент проверяет статус и схему, а расчёт остаётся той же отдельной функцией. Это удобный учебный обмен между двумя слоями, но ещё не публичный сервис. Не объявляйте его готовой производственной системой без требований к доступу, хранению, нагрузке и сопровождению. Начните с localhost и синтетических данных. В тестах чистого расчёта заменяйте источник: реальный запуск сервера нужен только проверке сетевого взаимодействия.
Асинхронный вариант полезен, когда независимые операции действительно ждут I/O и библиотеки поддерживают async. Он не ускоряет sum и не делает обычное чтение файла неблокирующим. Сохраните прежний формат результата, ограничьте число одновременно выполняемых операций и договоритесь, что делать с одной неудачей. TaskGroup помогает связать время жизни задач; их результаты затем оформляются последовательно. Перед обновлением модели сравните результаты с последовательной версией на одинаковых входных данных, а производительность измерьте отдельно от корректности.
Диагностика запуска и обновление Python
При переносе сначала проверьте sys.executable, версию Python и рабочую папку; затем входные файлы и установленные зависимости. Ошибка ModuleNotFoundError может означать другую среду, неверный способ запуска пакета или действительно отсутствующий пакет. Сообщение об относительном импорте часто связано с запуском файла напрямую вместо python -m. Проверьте также свои имена: файл json.py способен заслонить стандартную библиотеку. Эта короткая последовательность полезнее переустановки всей системы без причины. Диагностика должна помогать исправлению, а не требовать раскрытия полного окружения с секретами.
Версия этого руководства проверена на Python 3.15.0 по состоянию на 12 октября 2026 года. Минимальная версия отдельного примера указана в его метаданных; новый lazy import не запускается на 3.14. При обновлении сначала создайте новую среду, установите зависимости и выполните известные тесты и примеры, затем проверьте предупреждения и изменения поведения. Сохраните рабочую прежнюю среду до подтверждения результата. Совпадение номера Python не гарантирует одинаковые возможности сборки, расширений или данных часовых поясов; такие требования указываются отдельно.
Практика: несколько источников и один итог
Объедините три асинхронных учебных источника в один упорядоченный отчёт. Каждый источник должен вернуть данные, а не печатать их. После выхода из TaskGroup получите результаты, вычислите общий итог и только затем оформите вывод. Проверьте вариант с пустым источником: пустая выборка может быть допустимой, если контракт считает её нулём. Для настоящего сервиса добавьте ограничение параллельности, таймаут и проверку схемы; в упражнении внешняя сеть не используется.
Посмотреть решение
import asyncio
async def load(values):
await asyncio.sleep(0)
if any(type(value) is not int or value < 0 for value in values):
raise ValueError("Неверные значения")
return sum(values)
async def main():
async with asyncio.TaskGroup() as group:
tasks = [group.create_task(load(values)) for values in [[2, 3], [], [4]]]
subtotals = [task.result() for task in tasks]
print("Источники:", subtotals)
print("Итого:", sum(subtotals))
asyncio.run(main())
Источники: [5, 0, 4] Итого: 9
Примеры и материалы с собой
Архив содержит наши самостоятельные примеры, тестовые данные, ожидаемый результат и README. Указанные команды установки и фрагменты REPL отделены от скриптов, которые проверены запуском.
Сверено с официальной документацией 2026-10-12. Стандартная библиотека и сторонние пакеты различаются; ссылки на источники есть в каждой главе.


