@@ -461,20 +461,38 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
461461 mp_obj_class_lookup (& lookup , self -> base .type );
462462 mp_obj_t member = dest [0 ];
463463 if (member != MP_OBJ_NULL ) {
464- #if MICROPY_PY_BUILTINS_PROPERTY
464+ #if MICROPY_PY_BUILTINS_PROPERTY
465465 if (MP_OBJ_IS_TYPE (member , & mp_type_property )) {
466- // object member is a property
467- // delegate the store to the property
468- // TODO should this be part of mp_convert_member_lookup?
466+ // object member is a property; delegate the load to the property
467+ // Note: This is an optimisation for code size and execution time.
468+ // The proper way to do it is have the functionality just below
469+ // in a __get__ method of the property object, and then it would
470+ // be called by the descriptor code down below. But that way
471+ // requires overhead for the nested mp_call's and overhead for
472+ // the code.
469473 const mp_obj_t * proxy = mp_obj_property_get (member );
470474 if (proxy [0 ] == mp_const_none ) {
471- // TODO
475+ nlr_raise ( mp_obj_new_exception_msg ( & mp_type_AttributeError , "unreadable attribute" ));
472476 } else {
473477 dest [0 ] = mp_call_function_n_kw (proxy [0 ], 1 , 0 , & self_in );
474- // TODO should we convert the returned value using mp_convert_member_lookup?
475478 }
479+ return ;
476480 }
477- #endif
481+ #endif
482+
483+ #if MICROPY_PY_DESCRIPTORS
484+ // found a class attribute; if it has a __get__ method then call it with the
485+ // class instance and class as arguments and return the result
486+ // Note that this is functionally correct but very slow: each load_attr
487+ // requires an extra mp_load_method_maybe to check for the __get__.
488+ mp_obj_t attr_get_method [4 ];
489+ mp_load_method_maybe (member , MP_QSTR___get__ , attr_get_method );
490+ if (attr_get_method [0 ] != MP_OBJ_NULL ) {
491+ attr_get_method [2 ] = self_in ;
492+ attr_get_method [3 ] = mp_obj_get_type (self_in );
493+ dest [0 ] = mp_call_method_n_kw (2 , 0 , attr_get_method );
494+ }
495+ #endif
478496 return ;
479497 }
480498
@@ -495,9 +513,11 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
495513bool mp_obj_instance_store_attr (mp_obj_t self_in , qstr attr , mp_obj_t value ) {
496514 mp_obj_instance_t * self = self_in ;
497515
498- #if MICROPY_PY_BUILTINS_PROPERTY
499- // for property, we need to do a lookup first in the class dict
500- // this makes all stores slow... how to fix?
516+ #if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS
517+ // With property and/or descriptors enabled we need to do a lookup
518+ // first in the class dict for the attribute to see if the store should
519+ // be delegated.
520+ // Note: this makes all stores slow... how to fix?
501521 mp_obj_t member [2 ] = {MP_OBJ_NULL };
502522 struct class_lookup_data lookup = {
503523 .obj = self ,
@@ -507,20 +527,43 @@ bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
507527 .is_type = false,
508528 };
509529 mp_obj_class_lookup (& lookup , self -> base .type );
510- if (member [0 ] != MP_OBJ_NULL && MP_OBJ_IS_TYPE (member [0 ], & mp_type_property )) {
511- // attribute already exists and is a property
512- // delegate the store to the property
513- const mp_obj_t * proxy = mp_obj_property_get (member [0 ]);
514- if (proxy [1 ] == mp_const_none ) {
515- // TODO better error message
516- return false;
517- } else {
518- mp_obj_t dest [2 ] = {self_in , value };
519- mp_call_function_n_kw (proxy [1 ], 2 , 0 , dest );
530+
531+ if (member [0 ] != MP_OBJ_NULL ) {
532+ #if MICROPY_PY_BUILTINS_PROPERTY
533+ if (MP_OBJ_IS_TYPE (member [0 ], & mp_type_property )) {
534+ // attribute exists and is a property; delegate the store
535+ // Note: This is an optimisation for code size and execution time.
536+ // The proper way to do it is have the functionality just below
537+ // in a __set__ method of the property object, and then it would
538+ // be called by the descriptor code down below. But that way
539+ // requires overhead for the nested mp_call's and overhead for
540+ // the code.
541+ const mp_obj_t * proxy = mp_obj_property_get (member [0 ]);
542+ if (proxy [1 ] == mp_const_none ) {
543+ // TODO better error message?
544+ return false;
545+ } else {
546+ mp_obj_t dest [2 ] = {self_in , value };
547+ mp_call_function_n_kw (proxy [1 ], 2 , 0 , dest );
548+ return true;
549+ }
550+ }
551+ #endif
552+
553+ #if MICROPY_PY_DESCRIPTORS
554+ // found a class attribute; if it has a __set__ method then call it with the
555+ // class instance and value as arguments
556+ mp_obj_t attr_set_method [4 ];
557+ mp_load_method_maybe (member [0 ], MP_QSTR___set__ , attr_set_method );
558+ if (attr_set_method [0 ] != MP_OBJ_NULL ) {
559+ attr_set_method [2 ] = self_in ;
560+ attr_set_method [3 ] = value ;
561+ mp_call_method_n_kw (2 , 0 , attr_set_method );
520562 return true;
521563 }
564+ #endif
522565 }
523- #endif
566+ #endif
524567
525568 if (value == MP_OBJ_NULL ) {
526569 // delete attribute
0 commit comments