summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstijn <stinos@zoho.com>2015-02-14 18:43:54 +0100
committerDamien George <damien.p.george@gmail.com>2015-03-26 23:55:14 +0000
commit28fa84b4454ef253dfba5f1ceb023be2184a7271 (patch)
tree628ba6762450f90282c305eb0b8f4bc5dde8778e
parentc260836beb10b1d81c98732c2b7282eb6675140a (diff)
py: Add optional support for descriptors' __get__ and __set__ methods.
Disabled by default. Enabled on unix and windows ports.
-rw-r--r--py/mpconfig.h6
-rw-r--r--py/objtype.c85
-rw-r--r--py/qstrdefs.h4
-rw-r--r--py/vm.c5
-rw-r--r--tests/basics/class_descriptor.py20
-rw-r--r--unix/mpconfigport.h1
-rw-r--r--windows/mpconfigport.h1
7 files changed, 99 insertions, 23 deletions
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 094d8d78a..ae5e77625 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -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)
diff --git a/py/objtype.c b/py/objtype.c
index f64e5054a..c08a6357c 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -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_t member = dest[0];
if (member != MP_OBJ_NULL) {
-#if MICROPY_PY_BUILTINS_PROPERTY
+ #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
+ #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,20 +527,43 @@ 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
- 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);
+
+ 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?
+ 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;
}
+ #endif
}
-#endif
+ #endif
if (value == MP_OBJ_NULL) {
// delete attribute
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index dc1f082da..3c4106e85 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -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__)
diff --git a/py/vm.c b/py/vm.c
index ee218694c..29a9e4f6d 100644
--- a/py/vm.c
+++ b/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): {
diff --git a/tests/basics/class_descriptor.py b/tests/basics/class_descriptor.py
new file mode 100644
index 000000000..3f4b200bc
--- /dev/null
+++ b/tests/basics/class_descriptor.py
@@ -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'
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index fd8e52267..014272e7a 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -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)
diff --git a/windows/mpconfigport.h b/windows/mpconfigport.h
index 6e342f0d3..01f8775fa 100644
--- a/windows/mpconfigport.h
+++ b/windows/mpconfigport.h
@@ -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)