Перейти к содержимому
Поиск
Разработка

Python: полный мануал с примерами и справочником функций

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

Александр Некрасов
Python: практический мануал с кодом, папкой и схемой работы

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

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

Код можно копировать и скачивать. 195 самостоятельных примеров проверены запуском на Python 3.15.0; у упражнений есть решения и ожидаемый результат. Новые возможности языка отмечены в тексте, а интерактивные фрагменты и команды установки объяснены отдельно.

Если начинаете с нуля, открывайте главы по порядку. Если решаете конкретную задачу, ищите по-русски или по имени Python: open, str.split, Path.read_text.

Python 3.15.025 главПримеры проверены запуском
Все главы руководства 25

Глава 17 · продвинутый

Модель данных: как свои объекты участвуют в операциях Python

Свяжите обычные операции с протоколами объекта: итерацией, сравнением, контекстом, доступом к атрибутам и MRO.

Операция задаёт ожидаемое поведение

Когда программа пишет 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, или используйте осмысленную отдельную структуру хранения. После отказа записи проверьте оба экземпляра.

Примеры и материалы с собой

Архив содержит наши самостоятельные примеры, тестовые данные, ожидаемый результат и README. Указанные команды установки и фрагменты REPL отделены от скриптов, которые проверены запуском.

Сверено с официальной документацией 2026-10-12. Стандартная библиотека и сторонние пакеты различаются; ссылки на источники есть в каждой главе.