Шпора для тех собеса QA

🐍 Как работают контекстные менеджеры, bool и наследование под капотом Pytho

Разбираем внутренности, чтобы писать более эффективный код. Поехали!

1️⃣ Контекстный менеджер (with)

За знакомым синтаксисом скрывается протокол из двух методов:

· __enter__(self) – вызывается при входе в блок. Возвращает объект, который связывается с as. · __exit__(self, exc_type, exc_val, exc_tb) – вызывается при выходе. Если вернуть True, исключения внутри блока подавляются.

👉 Пример упрощённого open:

class ManagedFile: def __init__(self, name): self.name = name def __enter__(self): self.file = open(self.name, ‘w’) return self.file def __exit__(self, *args): self.file.close()

Когда интерпретатор встречает with, он:

1. Вызывает __enter__ и запоминает результат. 2. Выполняет тело блока. 3. В любом случае (ошибка/успех) вызывает __exit__ с деталями исключения (или None).

💡 Можно использовать contextlib.contextmanager – генератор превращается в менеджер через yield.

2️⃣ Логический тип (bool)

bool в Python – это подкласс int с двумя синглтонами:

· True == 1 · False == 0

Проверим:

issubclass(bool, int) True True + True 2 False * 10 0

🔧 Как работает приведение к bool? Для любого объекта вызывается __bool__() (или __len__() как fallback). Если __bool__ не определён, объект считается True (кроме None, пустых коллекций, нулей).

Пример: у списка __len__ возвращает 0 → bool([]) → False.

⚡️ В байткоде сравнения (a == b) превращаются в вызов PyObject_RichCompare, который возвращает True/False как объекты типа bool.

3️⃣ Наследование и MRO

Под капотом у каждого класса есть __bases__ (кортеж родителей) и __dict__ (пространство имён). При поиске атрибута obj.attr Python:

1. Смотрит в __dict__ самого объекта. 2. Поднимается по цепочке классов в порядке MRO (Method Resolution Order). 3. MRO вычисляется по алгоритму C3 (линеаризация) – он гарантирует, что родители просматриваются слева направо, а общие предки – только один раз в конце.

📌 Пример с ромбовидным наследованием:

class A: pass class B(A): pass class C(A): pass class D(B, C): pass

print(D.__mro__) # (, , , , )

super() – не просто «вызов родителя», а прокси, который динамически определяет следующий класс в MRO.

💥 Важно:

· Поиск атрибутов не рекурсивный – он идёт строго по MRO. · __getattribute__ перехватывает любой доступ, а __getattr__ – только если атрибут не найден стандартным путём.

📢 Вывод: понимание этих механизмов помогает отлаживать странное поведение, писать кастомные менеджеры и эффективно использовать super().