44use 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