mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
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:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user