From 1b89c503db690967d50699abe0bfa942f6f6b15e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 1 Oct 2024 10:26:27 +1000 Subject: [PATCH] py/objtype: Don't delegate lookup of descriptor methods to __getattr__. When descriptors are enabled, lookup of the `__get__`, `__set__` and `__delete__` descriptor methods should not be delegated to `__getattr__`. That follows CPython behaviour. Signed-off-by: Damien George --- py/objtype.c | 7 +++++++ tests/basics/class_descriptor.py | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/py/objtype.c b/py/objtype.c index 847216982a46..b9af1008995e 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -660,6 +660,13 @@ static void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des // try __getattr__ if (attr != MP_QSTR___getattr__) { + #if MICROPY_PY_DESCRIPTORS + // With descriptors enabled, don't delegate lookups of __get__/__set__/__delete__. + if (attr == MP_QSTR___get__ || attr == MP_QSTR___set__ || attr == MP_QSTR___delete__) { + return; + } + #endif + #if MICROPY_PY_DELATTR_SETATTR // If the requested attr is __setattr__/__delattr__ then don't delegate the lookup // to __getattr__. If we followed CPython's behaviour then __setattr__/__delattr__ diff --git a/tests/basics/class_descriptor.py b/tests/basics/class_descriptor.py index 54f386230fec..83d31674301d 100644 --- a/tests/basics/class_descriptor.py +++ b/tests/basics/class_descriptor.py @@ -21,14 +21,41 @@ class Main: try: m.__class__ except AttributeError: + # Target doesn't support __class__. print("SKIP") raise SystemExit r = m.Forward if 'Descriptor' in repr(r.__class__): + # Target doesn't support descriptors. print('SKIP') raise SystemExit +# Test assignment and deletion. + print(r) m.Forward = 'a' del m.Forward + +# Test that lookup of descriptors like __get__ are not passed into __getattr__. + + +class NonDescriptor: + def __getattr__(self, attr): + print("getattr", attr) + + +class TestClass: + non_descriptor = NonDescriptor() + + +print(isinstance(TestClass().non_descriptor, NonDescriptor)) + +t = TestClass() +t.non_descriptor = 123 +print(t.non_descriptor) + +try: + del TestClass().non_descriptor +except AttributeError: + print("AttributeError")