py: Add optional support for descriptors' __get__ and __set__ methods.
Disabled by default. Enabled on unix and windows ports.
This commit is contained in:
parent
c260836beb
commit
28fa84b445
|
@ -364,6 +364,12 @@ typedef double mp_float_t;
|
|||
#define MICROPY_PY_FUNCTION_ATTRS (0)
|
||||
#endif
|
||||
|
||||
// Whether to support descriptors (__get__ and __set__)
|
||||
// This costs some code size and makes all load attrs and store attrs slow
|
||||
#ifndef MICROPY_PY_DESCRIPTORS
|
||||
#define MICROPY_PY_DESCRIPTORS (0)
|
||||
#endif
|
||||
|
||||
// Whether str object is proper unicode
|
||||
#ifndef MICROPY_PY_BUILTINS_STR_UNICODE
|
||||
#define MICROPY_PY_BUILTINS_STR_UNICODE (0)
|
||||
|
|
67
py/objtype.c
67
py/objtype.c
|
@ -463,16 +463,34 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
|
|||
if (member != MP_OBJ_NULL) {
|
||||
#if MICROPY_PY_BUILTINS_PROPERTY
|
||||
if (MP_OBJ_IS_TYPE(member, &mp_type_property)) {
|
||||
// object member is a property
|
||||
// delegate the store to the property
|
||||
// TODO should this be part of mp_convert_member_lookup?
|
||||
// object member is a property; delegate the load to the property
|
||||
// Note: This is an optimisation for code size and execution time.
|
||||
// The proper way to do it is have the functionality just below
|
||||
// in a __get__ method of the property object, and then it would
|
||||
// be called by the descriptor code down below. But that way
|
||||
// requires overhead for the nested mp_call's and overhead for
|
||||
// the code.
|
||||
const mp_obj_t *proxy = mp_obj_property_get(member);
|
||||
if (proxy[0] == mp_const_none) {
|
||||
// TODO
|
||||
nlr_raise(mp_obj_new_exception_msg(&mp_type_AttributeError, "unreadable attribute"));
|
||||
} else {
|
||||
dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in);
|
||||
// TODO should we convert the returned value using mp_convert_member_lookup?
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MICROPY_PY_DESCRIPTORS
|
||||
// found a class attribute; if it has a __get__ method then call it with the
|
||||
// class instance and class as arguments and return the result
|
||||
// Note that this is functionally correct but very slow: each load_attr
|
||||
// requires an extra mp_load_method_maybe to check for the __get__.
|
||||
mp_obj_t attr_get_method[4];
|
||||
mp_load_method_maybe(member, MP_QSTR___get__, attr_get_method);
|
||||
if (attr_get_method[0] != MP_OBJ_NULL) {
|
||||
attr_get_method[2] = self_in;
|
||||
attr_get_method[3] = mp_obj_get_type(self_in);
|
||||
dest[0] = mp_call_method_n_kw(2, 0, attr_get_method);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
|
@ -495,9 +513,11 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
|
|||
bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
|
||||
mp_obj_instance_t *self = self_in;
|
||||
|
||||
#if MICROPY_PY_BUILTINS_PROPERTY
|
||||
// for property, we need to do a lookup first in the class dict
|
||||
// this makes all stores slow... how to fix?
|
||||
#if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS
|
||||
// With property and/or descriptors enabled we need to do a lookup
|
||||
// first in the class dict for the attribute to see if the store should
|
||||
// be delegated.
|
||||
// Note: this makes all stores slow... how to fix?
|
||||
mp_obj_t member[2] = {MP_OBJ_NULL};
|
||||
struct class_lookup_data lookup = {
|
||||
.obj = self,
|
||||
|
@ -507,12 +527,20 @@ bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
|
|||
.is_type = false,
|
||||
};
|
||||
mp_obj_class_lookup(&lookup, self->base.type);
|
||||
if (member[0] != MP_OBJ_NULL && MP_OBJ_IS_TYPE(member[0], &mp_type_property)) {
|
||||
// attribute already exists and is a property
|
||||
// delegate the store to the property
|
||||
|
||||
if (member[0] != MP_OBJ_NULL) {
|
||||
#if MICROPY_PY_BUILTINS_PROPERTY
|
||||
if (MP_OBJ_IS_TYPE(member[0], &mp_type_property)) {
|
||||
// attribute exists and is a property; delegate the store
|
||||
// Note: This is an optimisation for code size and execution time.
|
||||
// The proper way to do it is have the functionality just below
|
||||
// in a __set__ method of the property object, and then it would
|
||||
// be called by the descriptor code down below. But that way
|
||||
// requires overhead for the nested mp_call's and overhead for
|
||||
// the code.
|
||||
const mp_obj_t *proxy = mp_obj_property_get(member[0]);
|
||||
if (proxy[1] == mp_const_none) {
|
||||
// TODO better error message
|
||||
// TODO better error message?
|
||||
return false;
|
||||
} else {
|
||||
mp_obj_t dest[2] = {self_in, value};
|
||||
|
@ -522,6 +550,21 @@ bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if MICROPY_PY_DESCRIPTORS
|
||||
// found a class attribute; if it has a __set__ method then call it with the
|
||||
// class instance and value as arguments
|
||||
mp_obj_t attr_set_method[4];
|
||||
mp_load_method_maybe(member[0], MP_QSTR___set__, attr_set_method);
|
||||
if (attr_set_method[0] != MP_OBJ_NULL) {
|
||||
attr_set_method[2] = self_in;
|
||||
attr_set_method[3] = value;
|
||||
mp_call_method_n_kw(2, 0, attr_set_method);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (value == MP_OBJ_NULL) {
|
||||
// delete attribute
|
||||
mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
|
||||
|
|
|
@ -66,6 +66,10 @@ Q(__add__)
|
|||
Q(__sub__)
|
||||
Q(__repr__)
|
||||
Q(__str__)
|
||||
#if MICROPY_PY_DESCRIPTORS
|
||||
Q(__get__)
|
||||
Q(__set__)
|
||||
#endif
|
||||
Q(__getattr__)
|
||||
Q(__del__)
|
||||
Q(__call__)
|
||||
|
|
5
py/vm.c
5
py/vm.c
|
@ -393,8 +393,9 @@ dispatch_loop:
|
|||
DISPATCH();
|
||||
}
|
||||
#else
|
||||
// This caching code works with MICROPY_PY_BUILTINS_PROPERTY enabled because
|
||||
// if the attr exists in self->members then it can't be a property. A
|
||||
// This caching code works with MICROPY_PY_BUILTINS_PROPERTY and/or
|
||||
// MICROPY_PY_DESCRIPTORS enabled because if the attr exists in
|
||||
// self->members then it can't be a property or have descriptors. A
|
||||
// consequence of this is that we can't use MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
|
||||
// in the fast-path below, because that store could override a property.
|
||||
ENTRY(MP_BC_STORE_ATTR): {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
class Descriptor:
|
||||
def __get__(self, obj, cls):
|
||||
print(type(obj) is Main)
|
||||
print(cls is Main)
|
||||
return 'result'
|
||||
|
||||
def __set__(self, obj, val):
|
||||
print(type(obj) is Main)
|
||||
print(val)
|
||||
|
||||
class Main:
|
||||
Forward = Descriptor()
|
||||
|
||||
m = Main()
|
||||
r = m.Forward
|
||||
if 'Descriptor' in repr(r.__class__):
|
||||
print('SKIP')
|
||||
else:
|
||||
print(r)
|
||||
m.Forward = 'a'
|
|
@ -58,6 +58,7 @@
|
|||
#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1)
|
||||
#define MICROPY_CAN_OVERRIDE_BUILTINS (1)
|
||||
#define MICROPY_PY_FUNCTION_ATTRS (1)
|
||||
#define MICROPY_PY_DESCRIPTORS (1)
|
||||
#define MICROPY_PY_BUILTINS_STR_UNICODE (1)
|
||||
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
|
||||
#define MICROPY_PY_BUILTINS_FROZENSET (1)
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
#define MICROPY_STREAMS_NON_BLOCK (1)
|
||||
#define MICROPY_OPT_COMPUTED_GOTO (0)
|
||||
#define MICROPY_CAN_OVERRIDE_BUILTINS (1)
|
||||
#define MICROPY_PY_DESCRIPTORS (1)
|
||||
#define MICROPY_PY_BUILTINS_STR_UNICODE (1)
|
||||
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
|
||||
#define MICROPY_PY_BUILTINS_FROZENSET (1)
|
||||
|
|
Loading…
Reference in New Issue