Commit 28fa84b4 authored by stijn's avatar stijn Committed by Damien George
Browse files

py: Add optional support for descriptors' __get__ and __set__ methods.

Disabled by default.  Enabled on unix and windows ports.
parent c260836b
...@@ -364,6 +364,12 @@ typedef double mp_float_t; ...@@ -364,6 +364,12 @@ typedef double mp_float_t;
#define MICROPY_PY_FUNCTION_ATTRS (0) #define MICROPY_PY_FUNCTION_ATTRS (0)
#endif #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 // Whether str object is proper unicode
#ifndef MICROPY_PY_BUILTINS_STR_UNICODE #ifndef MICROPY_PY_BUILTINS_STR_UNICODE
#define MICROPY_PY_BUILTINS_STR_UNICODE (0) #define MICROPY_PY_BUILTINS_STR_UNICODE (0)
......
...@@ -461,20 +461,38 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { ...@@ -461,20 +461,38 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_class_lookup(&lookup, self->base.type); mp_obj_class_lookup(&lookup, self->base.type);
mp_obj_t member = dest[0]; mp_obj_t member = dest[0];
if (member != MP_OBJ_NULL) { if (member != MP_OBJ_NULL) {
#if MICROPY_PY_BUILTINS_PROPERTY #if MICROPY_PY_BUILTINS_PROPERTY
if (MP_OBJ_IS_TYPE(member, &mp_type_property)) { if (MP_OBJ_IS_TYPE(member, &mp_type_property)) {
// object member is a property // object member is a property; delegate the load to the property
// delegate the store to the property // Note: This is an optimisation for code size and execution time.
// TODO should this be part of mp_convert_member_lookup? // 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); const mp_obj_t *proxy = mp_obj_property_get(member);
if (proxy[0] == mp_const_none) { if (proxy[0] == mp_const_none) {
// TODO nlr_raise(mp_obj_new_exception_msg(&mp_type_AttributeError, "unreadable attribute"));
} else { } else {
dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); 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 #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; return;
} }
...@@ -495,9 +513,11 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { ...@@ -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) { bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
mp_obj_instance_t *self = self_in; mp_obj_instance_t *self = self_in;
#if MICROPY_PY_BUILTINS_PROPERTY #if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS
// for property, we need to do a lookup first in the class dict // With property and/or descriptors enabled we need to do a lookup
// this makes all stores slow... how to fix? // 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}; mp_obj_t member[2] = {MP_OBJ_NULL};
struct class_lookup_data lookup = { struct class_lookup_data lookup = {
.obj = self, .obj = self,
...@@ -507,20 +527,43 @@ bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { ...@@ -507,20 +527,43 @@ bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
.is_type = false, .is_type = false,
}; };
mp_obj_class_lookup(&lookup, self->base.type); 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 if (member[0] != MP_OBJ_NULL) {
// delegate the store to the property #if MICROPY_PY_BUILTINS_PROPERTY
const mp_obj_t *proxy = mp_obj_property_get(member[0]); if (MP_OBJ_IS_TYPE(member[0], &mp_type_property)) {
if (proxy[1] == mp_const_none) { // attribute exists and is a property; delegate the store
// TODO better error message // Note: This is an optimisation for code size and execution time.
return false; // The proper way to do it is have the functionality just below
} else { // in a __set__ method of the property object, and then it would
mp_obj_t dest[2] = {self_in, value}; // be called by the descriptor code down below. But that way
mp_call_function_n_kw(proxy[1], 2, 0, dest); // 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?
return false;
} else {
mp_obj_t dest[2] = {self_in, value};
mp_call_function_n_kw(proxy[1], 2, 0, dest);
return true;
}
}
#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; return true;
} }
#endif
} }
#endif #endif
if (value == MP_OBJ_NULL) { if (value == MP_OBJ_NULL) {
// delete attribute // delete attribute
......
...@@ -66,6 +66,10 @@ Q(__add__) ...@@ -66,6 +66,10 @@ Q(__add__)
Q(__sub__) Q(__sub__)
Q(__repr__) Q(__repr__)
Q(__str__) Q(__str__)
#if MICROPY_PY_DESCRIPTORS
Q(__get__)
Q(__set__)
#endif
Q(__getattr__) Q(__getattr__)
Q(__del__) Q(__del__)
Q(__call__) Q(__call__)
......
...@@ -393,8 +393,9 @@ dispatch_loop: ...@@ -393,8 +393,9 @@ dispatch_loop:
DISPATCH(); DISPATCH();
} }
#else #else
// This caching code works with MICROPY_PY_BUILTINS_PROPERTY enabled because // This caching code works with MICROPY_PY_BUILTINS_PROPERTY and/or
// if the attr exists in self->members then it can't be a property. A // 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 // 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. // in the fast-path below, because that store could override a property.
ENTRY(MP_BC_STORE_ATTR): { ENTRY(MP_BC_STORE_ATTR): {
......
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 @@ ...@@ -58,6 +58,7 @@
#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1) #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1)
#define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1)
#define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_FUNCTION_ATTRS (1)
#define MICROPY_PY_DESCRIPTORS (1)
#define MICROPY_PY_BUILTINS_STR_UNICODE (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1)
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) #define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
#define MICROPY_PY_BUILTINS_FROZENSET (1) #define MICROPY_PY_BUILTINS_FROZENSET (1)
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#define MICROPY_STREAMS_NON_BLOCK (1) #define MICROPY_STREAMS_NON_BLOCK (1)
#define MICROPY_OPT_COMPUTED_GOTO (0) #define MICROPY_OPT_COMPUTED_GOTO (0)
#define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1)
#define MICROPY_PY_DESCRIPTORS (1)
#define MICROPY_PY_BUILTINS_STR_UNICODE (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1)
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) #define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
#define MICROPY_PY_BUILTINS_FROZENSET (1) #define MICROPY_PY_BUILTINS_FROZENSET (1)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment