Метаклассы и интроспекция
Метаклассы, интроспекция и рефлексия — управление созданием классов.
8 вопросов
JuniorТеорияОчень частоЧто такое метакласс в Python?
Что такое метакласс в Python?
Метакласс — это класс класса: то, что создаёт объекты-классы, точно так же как обычный class создаёт свои экземпляры. Метакласс по умолчанию — type; другой задаётся как class Foo(metaclass=Meta).
Типичные ошибки
- ✗Путать метакласс (класс класса) с базовым классом при обычном наследовании
- ✗Забывать, что сам
type— метакласс по умолчанию для каждого класса - ✗Считать экземпляры класса метаклассами, а не объект-класс, который их создал
Уточняющие вопросы
- →Как указать пользовательский метакласс при определении класса?
- →Что возвращает
type(SomeClass)для обычного класса?
JuniorТеорияЧастоВ каком смысле классы в Python сами являются объектами?
В каком смысле классы в Python сами являются объектами?
Класс — это полноценный объект: экземпляр type, своего метакласса. Его можно присвоить переменной, передать в функцию и создать во время выполнения. type(Foo) — это type, а Foo.__class__ — его метакласс.
Типичные ошибки
- ✗Считать, что
type(Foo)возвращаетobject, а не метаклассtype - ✗Полагать, что класс нельзя присвоить переменной или передать аргументом
- ✗Думать, что классы существуют лишь на этапе компиляции, но не в рантайме
Уточняющие вопросы
- →Чем отличается
type(Foo)отFoo.__bases__? - →Как создать класс динамически без оператора
class?
JuniorТеорияЧастоЧто такое интроспекция в Python?
Что такое интроспекция в Python?
Интроспекция — это когда программа исследует тип и свойства объекта во время выполнения, не меняя его: type(obj), isinstance(obj, C), dir(obj), hasattr(obj, n), а также obj.__dict__ и obj.__class__. Она только читает.
Типичные ошибки
- ✗Считать, что интроспекция меняет объект — она лишь читает его тип и атрибуты
- ✗Думать, что она работает на этапе компиляции, тогда как
type/dirразрешаются во время выполнения - ✗Предполагать, что нужен метакласс — обычные объекты полностью интроспектируемы
Уточняющие вопросы
- →В чём разница между
dir(obj)иobj.__dict__? - →Чем рефлексия выходит за пределы интроспекции?
MiddleТеорияИногдаЧто делает каждый аргумент getattr(obj, name, default) и чем он отличается от obj.name?
Что делает каждый аргумент getattr(obj, name, default) и чем он отличается от obj.name?
name это строка во время выполнения, поэтому getattr разрешает имена, которые obj.name выразить не может; если name нет, он возвращает default вместо AttributeError. Запускает тот же поиск __getattribute__/__getattr__, что и obj.name. Рядом: setattr, delattr, hasattr.
Типичные ошибки
- ✗Думать, что
default— это возврат при успехе, а не запасное значение для отсутствующего имени - ✗Считать, что
getattrобходит__getattribute__/__getattr__и читает__dict__напрямую - ✗Забывать, что
hasattr/delattrсуществуют, и изобретать их черезtry/exceptили__dict__
Уточняющие вопросы
- →Как
getattrвзаимодействует с__getattr__и__getattribute__? - →Когда
getattr(obj, name, default)предпочтительнее блокаtry/except AttributeError?
MiddleТеорияИногдаКак метакласс перехватывает и настраивает создание класса?
Как метакласс перехватывает и настраивает создание класса?
Когда тело class дочитано, Python вызывает __new__/__init__ метакласса с именем, базами и namespace. Он может осмотреть и изменить namespace, поправить базы, зарегистрировать класс и вернуть готовый объект-класс — один раз, при определении.
Типичные ошибки
- ✗Путать время создания класса (один раз) с созданием экземпляра при каждом вызове
- ✗Считать namespace доступным только для чтения, хотя метакласс волен его менять
- ✗Забывать, что метакласс получает имя, базы и namespace как входные данные
Уточняющие вопросы
- →Чем
__new__метакласса отличается от его__init__? - →Когда срабатывает
__init_subclass__по сравнению с хуком метакласса?
MiddleТеорияИногдаЧем рефлексия отличается от интроспекции в Python?
Чем рефлексия отличается от интроспекции в Python?
Интроспекция лишь исследует объект во время выполнения; рефлексия идёт дальше и манипулирует им — читает и пишет атрибуты через getattr/setattr/delattr, вызывает методы или создаёт объекты по имени, неизвестному при компиляции.
Типичные ошибки
- ✗Считать рефлексию и интроспекцию синонимами — рефлексия ещё и меняет, и действует
- ✗Полагать, что рефлексия только на чтение — она также пишет и удаляет атрибуты
- ✗Думать, что имена атрибутов должны быть в исходнике, а не строками во время выполнения
Уточняющие вопросы
- →Как вызвать метод, имя которого известно лишь во время выполнения?
- →Какие риски несёт динамическая запись атрибутов через
setattr?
MiddleТеорияИногдаКак type выступает одновременно функцией и метаклассом по умолчанию?
Как type выступает одновременно функцией и метаклассом по умолчанию?
С одним аргументом type(obj) возвращает тип объекта. С тремя аргументами type(name, bases, namespace) динамически создаёт новый класс — ровно то, что оператор class делает внутри через метакласс по умолчанию type.
Типичные ошибки
- ✗Думать, что
type(...)с тремя аргументами создаёт экземпляр, а не новый класс - ✗Считать, что
typeумеет только инспектировать, но не строить классы - ✗Забывать, что сам оператор
classпо умолчанию вызываетtype
Уточняющие вопросы
- →Какие три аргумента принимает
typeпри создании класса? - →Как отнаследоваться от
type, чтобы написать собственный метакласс?
SeniorТеорияРедкоКогда метаклассы действительно применяются на практике?
Когда метаклассы действительно применяются на практике?
В основном для декларативных API фреймворков: метакласс models.Model в Django превращает объявления полей в дескрипторы над БД и регистрирует модель. Применяй для регистрации, валидации или добавления методов, когда декоратора уже мало.
Типичные ошибки
- ✗Брать метакласс там, где проще декоратор или
__init_subclass__ - ✗Считать, что метаклассы вообще нужны для работы обычного наследования
- ✗Думать, что их цель — производительность, а не декларативная сборка классов
Уточняющие вопросы
- →Как
__init_subclass__покрывает многие случаи, где раньше нужен был метакласс? - →Почему совмещение несовместимых метаклассов вызывает конфликт метаклассов?