Представьте, что нужно изменить результат проверки isinstance для объекта, не меняя его фактический тип: каким механизмом класса это можно сделать?
Для этого определяют __instancecheck__ в метаклассе проверяемого класса. При вызове isinstance(obj, C) Python может делегировать проверку метаклассу C, и тот возвращает результат по пользовательскому правилу.
Это меняет только поведение проверки isinstance, но не тип объекта, его MRO и фактическое наследование.
Обычная проверка типа недостаточно гибка для абстракций: объект может поддерживать нужный интерфейс, не наследуясь от конкретного класса. Механизм пользовательской проверки экземпляров позволяет отделить логическую принадлежность к категории от прямого наследования.
На этой идее основаны, в частности, абстрактные базовые классы: они могут считать объект подходящим по зарегистрированному виртуальному наследованию или по проверке интерфейса.
Иногда библиотека хочет принимать любой объект с определёнными возможностями, но обычная проверка isinstance ориентируется на реальную иерархию классов. Прямое добавление классов в наследники может быть невозможно или нежелательно: оно меняет MRO, связывает независимые компоненты и может создавать конфликт методов.
Переопределение проверки также несёт риск: результат isinstance может перестать очевидно следовать из типа объекта. Если логика проверки сложная, противоречивая или зависит от изменяемого состояния, это ухудшает предсказуемость API.
__instancecheck__ ищется не в экземпляре проверяемого класса, а в его метаклассе. Поэтому специальный метод объявляют у класса, который управляет поведением самого класса.
В этом примере Buffer не наследуется от FileLike, но проверка isinstance вызывает InterfaceMeta.__instancecheck__. Сам объект при этом остаётся экземпляром Buffer, а FileLike не появляется в его MRO.
Механизм следует применять осторожно. Проверка должна быть быстрой, детерминированной и по возможности основанной на стабильных признаках; вызов произвольных методов внутри неё может иметь побочные эффекты. Кроме того, __instancecheck__ отвечает только за isinstance: проверка issubclass использует отдельный протокол — __subclasscheck__.
Если используется abc.ABCMeta, обычно лучше применять его штатные возможности, например регистрацию виртуальных подклассов или __subclasshook__, вместо ручного метакласса. Это делает намерение понятнее и позволяет использовать согласованную логику ABC.
Библиотека сериализации принимает файловоподобные объекты. Вариант с проверкой конкретных классов плохо масштабируется: разные реализации файлов, сетевых потоков и буферов имеют разные типы. Прямое наследование от общего класса тоже неудобно, поскольку внешний класс нельзя безопасно изменить.
Можно проверять наличие методов непосредственно в каждом месте вызова. Плюс этого подхода — простота локальной логики; минус — дублирование условий и расхождение проверок между компонентами.
Другой вариант — определить абстрактный класс с пользовательской проверкой экземпляров. Тогда единое правило можно использовать через isinstance, а сами сторонние классы не требуют изменения. Это решение выбирают, если критерий интерфейса стабилен и проверка не вызывает дорогих или побочных операций.
__instancecheck__?Он должен находиться в метаклассе класса, переданного вторым аргументом isinstance. Метод, объявленный как обычный атрибут самого проверяемого класса, не является стандартным способом настройки этой проверки: специальные методы для операций над классами ищутся на метаклассе.
__instancecheck__ настоящее наследование?Нет. Он не меняет type(obj), obj.__class__, MRO или результат обычного поиска методов. Поэтому объект может считаться экземпляром некоторого класса для isinstance, но не получать его методы и не становиться его подклассом в смысле структуры классов.
isinstance и issubclass?Нет. isinstance(obj, C) проверяет принадлежность объекта классу, а issubclass(D, C) — отношение между классами. Для второй операции используется отдельный механизм __subclasscheck__; изменение __instancecheck__ само по себе не меняет результат issubclass.