You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Deleting __getattr__ from a base class stops a subclass's own __getattribute__ from being called #9796
After del Base.__getattr__, attribute access on an instance of a subclass that defines its own __getattribute__ no longer calls that method. RustPython falls back to the default attribute lookup, and the class stays that way. CPython keeps calling the subclass's __getattribute__.
So proxies, lazy objects and access hooks written with __getattribute__ silently stop working once __getattr__ has been added to and removed from a base class. One common way to get there is unittest.mock.patch.object(Base, "__getattr__", ..., create=True). When the patch ends, mock deletes the attribute again, and from then on every subclass of Base that defines __getattribute__ skips it for the rest of the process. Deleting __getattr__ from the class that defines __getattribute__ itself has the same effect.
The two lines before the last print can be replaced with with unittest.mock.patch.object(Base, "__getattr__", lambda self, name: "fallback", create=True): pass, and the output is the same.
__getattribute__ and __getattr__ share the getattro slot, and its update code is in slot.rs#L1230-L1269. When a name is added and no __getattr__ is left in the MRO, the code looks up __getattribute__ with lookup_slot_in_mro. When a name is removed (ADD is false), the last branch (L1267-L1269) calls inherit_from_mro instead. That copies the slot of the first base class that has one and never looks at a __getattribute__ in the class's own dict. del Base.__getattr__ updates the slot of Base and of its subclasses, so Proxy ends up with object's generic getattro. The __setattr__/__delattr__ branch right below (L1271-L1304) already resolves both names again on removal.
I tried resolving __getattribute__ the same way in both cases: drop the ADD condition, look up identifier!(ctx, __getattribute__) instead of name, and remove the last else. With that change, the reproducer, the mock.patch.object variant and the same-class case all match CPython. test_descr, test_class, test_types, extra_tests/snippets/builtin_object.py and extra_tests/snippets/vm_specialization.py still pass.
Drafted with help from Claude Code; I reviewed and checked it before posting.
Activity