Skip to content

Commit 69fc75b

Browse files
authored
Skip __class__ lookup in object_isinstance for standard getattro (RustPython#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().
1 parent f443226 commit 69fc75b

1 file changed

Lines changed: 25 additions & 12 deletions

File tree

crates/vm/src/protocol/object.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
use crate::{
55
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
66
builtins::{
7-
PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef,
8-
PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr,
7+
PyBaseObject, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple,
8+
PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr,
99
},
1010
common::{hash::PyHash, str::to_ascii},
1111
convert::{ToPyObject, ToPyResult},
@@ -565,9 +565,12 @@ impl PyObject {
565565
// PyType_Check(cls) - cls is a type object
566566
let mut retval = self.class().is_subtype(cls);
567567
if !retval {
568-
// Check __class__ attribute, only masking AttributeError
569-
if let Some(i_cls) =
570-
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
568+
// __class__ is a data descriptor on object that always returns
569+
// obj.class() under standard __getattribute__. Only do the
570+
// expensive attribute lookup when getattro is overridden.
571+
if !self.has_standard_getattro()
572+
&& let Some(i_cls) =
573+
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
571574
&& let Ok(i_cls_type) = PyTypeRef::try_from_object(vm, i_cls)
572575
&& !i_cls_type.is(self.class())
573576
{
@@ -584,14 +587,15 @@ impl PyObject {
584587
)
585588
})?;
586589

587-
// Get __class__ attribute and check, only masking AttributeError
588-
if let Some(i_cls) =
589-
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
590-
{
591-
i_cls.abstract_issubclass(cls, vm)
590+
let i_cls: PyObjectRef = if self.has_standard_getattro() {
591+
self.class().to_owned().into()
592592
} else {
593-
Ok(false)
594-
}
593+
match vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? {
594+
Some(cls) => cls,
595+
None => return Ok(false),
596+
}
597+
};
598+
i_cls.abstract_issubclass(cls, vm)
595599
}
596600
}
597601

@@ -769,6 +773,15 @@ impl PyObject {
769773
Err(vm.new_type_error(format!("'{}' does not support item deletion", self.class())))
770774
}
771775

776+
/// Returns true if the object uses the standard `__getattribute__`
777+
/// (i.e. `object.__getattribute__`), meaning attribute access follows
778+
/// the normal descriptor protocol without custom interception.
779+
#[inline]
780+
fn has_standard_getattro(&self) -> bool {
781+
let getattro = self.class().slots.getattro.load().unwrap();
782+
getattro as usize == PyBaseObject::getattro as *const () as usize
783+
}
784+
772785
/// Equivalent to CPython's _PyObject_LookupSpecial
773786
/// Looks up a special method in the type's MRO without checking instance dict.
774787
/// Returns None if not found (masking AttributeError like CPython).

0 commit comments

Comments
 (0)