Background
The current documentation is fairly vague about how isinstance works:
Return
Trueif the object argument is an instance of the classinfo argument, or of a (direct, indirect, or virtual) subclass thereof. If object is not an object of the given type, the function always returnsFalse. If classinfo is a tuple of type objects (or recursively, other such tuples) or a Union Type of multiple types, returnTrueif object is an instance of any of the types. If classinfo is not a type or tuple of types and such tuples, aTypeErrorexception is raised.TypeErrormay not be raised for an invalid type if an earlier check succeeds.
(Built-in Functions — Python 3.14.5 documentation)
The unittest.mock.Mock docs imply that overriding __class__ makes isinstance return True for the mocked type:
__class__Normally the
__class__attribute of an object will return its type. For a mock object with aspec,__class__returns the spec class instead. This allows mock objects to passisinstance()tests for the object they are replacing / masquerading as:
…
(unittest.mock — mock object library — Python 3.14.5 documentation)
The comment in unittest.mock._is_instance_mock implies that faking __class__ overrides isinstance to ignore the true type of the object:
def _is_instance_mock(obj):
# can't use isinstance on Mock objects because they override __class__
# The base class for all mocks is NonCallableMock
return issubclass(type(obj), NonCallableMock)
In practice, however, isinstance checks both type(obj) and obj.__class__.
object_isinstance implementation in CPython
static int
object_isinstance(PyObject *inst, PyObject *cls)
{
PyObject *icls;
int retval;
if (PyType_Check(cls)) {
retval = PyObject_TypeCheck(inst, (PyTypeObject *)cls);
if (retval == 0) {
retval = PyObject_GetOptionalAttr(inst, &_Py_ID(__class__), &icls);
if (icls != NULL) {
if (icls != (PyObject *)(Py_TYPE(inst)) && PyType_Check(icls)) {
retval = PyType_IsSubtype(
(PyTypeObject *)icls,
(PyTypeObject *)cls);
}
else {
retval = 0;
}
Py_DECREF(icls);
}
}
}
else {
if (!check_class(cls,
"isinstance() arg 2 must be a type, a tuple of types, or a union"))
return -1;
retval = PyObject_GetOptionalAttr(inst, &_Py_ID(__class__), &icls);
if (icls != NULL) {
retval = abstract_issubclass(icls, cls);
Py_DECREF(icls);
}
}
return retval;
}
This has been the case since CPython 2.2, and PyPy matches the CPython behavior.
Proposal
I propose ammending the documentation for isinstance to more clearly reflect its actual behavior:
Return
Trueif the object argument is an instance of the classinfo argument, or of a (direct, indirect, or virtual) subclass thereof. If the object argument has a__class__attribute that differs from object’s actual type, additonally returnTrueif__class__is a (direct, indirect, or virtual) subclass of the classinfo argument. Otherwise, the function returnsFalse. If classinfo …