Skip to content

Commit 28fa84b

Browse files
stinosdpgeorge
authored andcommitted
py: Add optional support for descriptors' __get__ and __set__ methods.
Disabled by default. Enabled on unix and windows ports.
1 parent c260836 commit 28fa84b

7 files changed

Lines changed: 99 additions & 23 deletions

File tree

py/mpconfig.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ typedef double mp_float_t;
364364
#define MICROPY_PY_FUNCTION_ATTRS (0)
365365
#endif
366366

367+
// Whether to support descriptors (__get__ and __set__)
368+
// This costs some code size and makes all load attrs and store attrs slow
369+
#ifndef MICROPY_PY_DESCRIPTORS
370+
#define MICROPY_PY_DESCRIPTORS (0)
371+
#endif
372+
367373
// Whether str object is proper unicode
368374
#ifndef MICROPY_PY_BUILTINS_STR_UNICODE
369375
#define MICROPY_PY_BUILTINS_STR_UNICODE (0)

py/objtype.c

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {
495513
bool 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

py/qstrdefs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ Q(__add__)
6666
Q(__sub__)
6767
Q(__repr__)
6868
Q(__str__)
69+
#if MICROPY_PY_DESCRIPTORS
70+
Q(__get__)
71+
Q(__set__)
72+
#endif
6973
Q(__getattr__)
7074
Q(__del__)
7175
Q(__call__)

py/vm.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,9 @@ mp_vm_return_kind_t mp_execute_bytecode(mp_code_state *code_state, volatile mp_o
393393
DISPATCH();
394394
}
395395
#else
396-
// This caching code works with MICROPY_PY_BUILTINS_PROPERTY enabled because
397-
// if the attr exists in self->members then it can't be a property. A
396+
// This caching code works with MICROPY_PY_BUILTINS_PROPERTY and/or
397+
// MICROPY_PY_DESCRIPTORS enabled because if the attr exists in
398+
// self->members then it can't be a property or have descriptors. A
398399
// consequence of this is that we can't use MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
399400
// in the fast-path below, because that store could override a property.
400401
ENTRY(MP_BC_STORE_ATTR): {

tests/basics/class_descriptor.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class Descriptor:
2+
def __get__(self, obj, cls):
3+
print(type(obj) is Main)
4+
print(cls is Main)
5+
return 'result'
6+
7+
def __set__(self, obj, val):
8+
print(type(obj) is Main)
9+
print(val)
10+
11+
class Main:
12+
Forward = Descriptor()
13+
14+
m = Main()
15+
r = m.Forward
16+
if 'Descriptor' in repr(r.__class__):
17+
print('SKIP')
18+
else:
19+
print(r)
20+
m.Forward = 'a'

unix/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1)
5959
#define MICROPY_CAN_OVERRIDE_BUILTINS (1)
6060
#define MICROPY_PY_FUNCTION_ATTRS (1)
61+
#define MICROPY_PY_DESCRIPTORS (1)
6162
#define MICROPY_PY_BUILTINS_STR_UNICODE (1)
6263
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
6364
#define MICROPY_PY_BUILTINS_FROZENSET (1)

windows/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#define MICROPY_STREAMS_NON_BLOCK (1)
5252
#define MICROPY_OPT_COMPUTED_GOTO (0)
5353
#define MICROPY_CAN_OVERRIDE_BUILTINS (1)
54+
#define MICROPY_PY_DESCRIPTORS (1)
5455
#define MICROPY_PY_BUILTINS_STR_UNICODE (1)
5556
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
5657
#define MICROPY_PY_BUILTINS_FROZENSET (1)

0 commit comments

Comments
 (0)