Skip __class__ lookup in object_isinstance for standard getattro (#7303)

In object_isinstance(), when is_subtype() returns false, the __class__
attribute lookup via get_attribute_opt is redundant for objects using
standard __getattribute__, since __class__ is a data descriptor on
object that always returns obj.class().
This commit is contained in:
Jeong, YunWon
2026-03-02 10:18:45 +09:00
committed by GitHub
parent f443226e46
commit 69fc75b22d

View File

@@ -4,8 +4,8 @@
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{
PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef,
PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr,
PyBaseObject, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple,
PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr,
},
common::{hash::PyHash, str::to_ascii},
convert::{ToPyObject, ToPyResult},
@@ -565,9 +565,12 @@ impl PyObject {
// PyType_Check(cls) - cls is a type object
let mut retval = self.class().is_subtype(cls);
if !retval {
// Check __class__ attribute, only masking AttributeError
if let Some(i_cls) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
// __class__ is a data descriptor on object that always returns
// obj.class() under standard __getattribute__. Only do the
// expensive attribute lookup when getattro is overridden.
if !self.has_standard_getattro()
&& let Some(i_cls) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
&& let Ok(i_cls_type) = PyTypeRef::try_from_object(vm, i_cls)
&& !i_cls_type.is(self.class())
{
@@ -584,14 +587,15 @@ impl PyObject {
)
})?;
// Get __class__ attribute and check, only masking AttributeError
if let Some(i_cls) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
{
i_cls.abstract_issubclass(cls, vm)
let i_cls: PyObjectRef = if self.has_standard_getattro() {
self.class().to_owned().into()
} else {
Ok(false)
}
match vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? {
Some(cls) => cls,
None => return Ok(false),
}
};
i_cls.abstract_issubclass(cls, vm)
}
}
@@ -769,6 +773,15 @@ impl PyObject {
Err(vm.new_type_error(format!("'{}' does not support item deletion", self.class())))
}
/// Returns true if the object uses the standard `__getattribute__`
/// (i.e. `object.__getattribute__`), meaning attribute access follows
/// the normal descriptor protocol without custom interception.
#[inline]
fn has_standard_getattro(&self) -> bool {
let getattro = self.class().slots.getattro.load().unwrap();
getattro as usize == PyBaseObject::getattro as *const () as usize
}
/// Equivalent to CPython's _PyObject_LookupSpecial
/// Looks up a special method in the type's MRO without checking instance dict.
/// Returns None if not found (masking AttributeError like CPython).