Inheritance for native types is not supported
Default load_attr implementation (as used for all native types) doesn't take type inheritance (->bases_tuple) into account. I'm working on OrderedDict implementation, and it inherits most of its methods from dict, but adds few (well, 1) new method. Of course, this new method can be ignored, and old trick of putting the same .locals_dict to both types can be used. But I wanted to look into making truly inheritable methods, which requires setting custom .load_attr type method. And we already have implementation of this method which takes inheritance into account - for user classes/instances. But trying to use it, it assumes that object whose attr is being looked up is instance, so making it work with native objects means adding even more conditions to existing bunch of those (and which are already pretty mind-boggling). Another alternative is to make clean, "optimized" inheritance-friendly .load_attr for native types, but that means code duplication (and refactoring of existing code paths, factoring out functions, which means more stack usage, etc.)
Any thoughts?
py/objtype: Ensures native attr handlers process subclass attribute stores
Summary
Problem Description
When a Python class inherits from a native type that uses an attr handler to implement properties, setting attributes on instances fails to call the native property setters. Instead, the values are stored in the instance's members dictionary, shadowing the native properties.
Root Cause
In objtype.c:
mp_obj_instance_store_attr()did not check if the native base type has an attr handler before storing attributes in the instance's member dictionary- For LOAD operations,
mp_obj_class_lookup()has special handling that callsmp_load_method_maybe()on the native subobject, but there was no equivalent for STORE operations
Solution
Modified mp_obj_instance_store_attr() to:
- Check for native base type early: Before any other processing, check if the instance's type has a native base class with an attr handler
- Try the native attr handler first: Call the native type's attr handler with
MP_OBJ_SENTINELto attempt the store/delete operation - Handle exceptions gracefully: If the attr handler raises
AttributeErrororKeyError(indicating the attribute isn't recognized), catch the exception and proceed with normal dict storage - Fall back to normal processing: If the attr handler doesn't handle the attribute, continue with property/descriptor checks and dict storage
Fixes #18592
Testing
This adds a test on based on uctypes in tests/extmod/uctypes_subclass_attrs.py.
Test Coverage:
-
Path 1: Native attr handler successfully processes attribute (Tests 1-2)
- Native type and subclass correctly delegate to attr handler
- Values written to memory, not stored in
__dict__ - Verifies the bug fix works correctly
-
Path 2: KeyError fallback to dict storage (Test 3)
- Unknown attribute triggers
KeyErrorin native handler - Exception caught, falls back to instance
__dict__storage - Python attributes coexist with native attributes
- Unknown attribute triggers
-
Path 3: TypeError re-raise (Test 4)
- Invalid type (
stringinstead ofUINT32) triggersTypeErrorin handler - Not
KeyError/AttributeError→ re-raised vianlr_jump() - Critical defensive programming path
- Invalid type (
-
Path 4: Test everything still works
Trade-offs and Alternatives
TBD