Шпора для тех собеса 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().