From e44d26ae0c1b5d248fa4db112cdeabe404944f3c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 31 Mar 2014 22:57:56 +0100 Subject: [PATCH] py: Implement __getattr__. It's not completely satisfactory, because a failed call to __getattr__ should not raise an exception. __setattr__ could be implemented, but it would slow down all stores to a user created object. Need to implement some caching system. --- py/objtype.c | 14 ++++++++ py/qstrdefs.h | 1 + py/runtime.c | 79 ++++++++++++++++++++--------------------- py/runtime.h | 1 + tests/basics/getattr.py | 11 ++++++ 5 files changed, 65 insertions(+), 41 deletions(-) create mode 100644 tests/basics/getattr.py diff --git a/py/objtype.c b/py/objtype.c index 06bc803ebe..11200c9135 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -16,6 +16,7 @@ typedef struct _mp_obj_class_t { mp_obj_base_t base; mp_map_t members; + // TODO maybe cache __getattr__ and __setattr__ for efficient lookup of them } mp_obj_class_t; STATIC mp_obj_t mp_obj_new_class(mp_obj_t class) { @@ -225,6 +226,19 @@ STATIC void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } else { // class member is a value, so just return that value dest[0] = member; + } + return; + } + + // try __getattr__ + if (attr != MP_QSTR___getattr__) { + mp_obj_t dest2[3]; + mp_load_method_maybe(self_in, MP_QSTR___getattr__, dest2); + if (dest2[0] != MP_OBJ_NULL) { + // __getattr__ exists, call it and return its result + // XXX if this fails to load the requested attr, should we catch the attribute error and return silently? + dest2[2] = MP_OBJ_NEW_QSTR(attr); + dest[0] = mp_call_method_n_kw(1, 0, dest2); return; } } diff --git a/py/qstrdefs.h b/py/qstrdefs.h index 457043938b..5c29ba1de0 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -26,6 +26,7 @@ Q(__add__) Q(__sub__) Q(__repr__) Q(__str__) +Q(__getattr__) Q(micropython) Q(byte_code) diff --git a/py/runtime.c b/py/runtime.c index f7a55545af..0c75d4cd31 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -701,7 +701,7 @@ mp_obj_t mp_load_attr(mp_obj_t base, qstr attr) { // no attribute found, returns: dest[0] == MP_OBJ_NULL, dest[1] == MP_OBJ_NULL // normal attribute found, returns: dest[0] == , dest[1] == MP_OBJ_NULL // method attribute found, returns: dest[0] == , dest[1] == -STATIC void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest) { +void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest) { // clear output to indicate no attribute/method found yet dest[0] = MP_OBJ_NULL; dest[1] = MP_OBJ_NULL; @@ -709,48 +709,45 @@ STATIC void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest) { // get the type mp_obj_type_t *type = mp_obj_get_type(base); - // if this type can do its own load, then call it - if (type->load_attr != NULL) { - type->load_attr(base, attr, dest); - } - - // if nothing found yet, look for built-in and generic names - if (dest[0] == MP_OBJ_NULL) { + // look for built-in names + if (0) { #if MICROPY_CPYTHON_COMPAT - if (attr == MP_QSTR___class__) { - // a.__class__ is equivalent to type(a) - dest[0] = type; - } else + } else if (attr == MP_QSTR___class__) { + // a.__class__ is equivalent to type(a) + dest[0] = type; #endif - if (attr == MP_QSTR___next__ && type->iternext != NULL) { - dest[0] = (mp_obj_t)&mp_builtin_next_obj; - dest[1] = base; - } else if (type->load_attr == NULL) { - // generic method lookup if type didn't provide a specific one - // this is a lookup in the object (ie not class or type) - if (type->locals_dict != NULL) { - assert(MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)); // Micro Python restriction, for now - mp_map_t *locals_map = mp_obj_dict_get_map(type->locals_dict); - mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); - if (elem != NULL) { - // check if the methods are functions, static or class methods - // see http://docs.python.org/3.3/howto/descriptor.html - if (MP_OBJ_IS_TYPE(elem->value, &mp_type_staticmethod)) { - // return just the function - dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun; - } else if (MP_OBJ_IS_TYPE(elem->value, &mp_type_classmethod)) { - // return a bound method, with self being the type of this object - dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun; - dest[1] = mp_obj_get_type(base); - } else if (mp_obj_is_callable(elem->value)) { - // return a bound method, with self being this object - dest[0] = elem->value; - dest[1] = base; - } else { - // class member is a value, so just return that value - dest[0] = elem->value; - } - } + + } else if (attr == MP_QSTR___next__ && type->iternext != NULL) { + dest[0] = (mp_obj_t)&mp_builtin_next_obj; + dest[1] = base; + + } else if (type->load_attr != NULL) { + // this type can do its own load, so call it + type->load_attr(base, attr, dest); + + } else if (type->locals_dict != NULL) { + // generic method lookup + // this is a lookup in the object (ie not class or type) + assert(MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)); // Micro Python restriction, for now + mp_map_t *locals_map = mp_obj_dict_get_map(type->locals_dict); + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + // check if the methods are functions, static or class methods + // see http://docs.python.org/3.3/howto/descriptor.html + if (MP_OBJ_IS_TYPE(elem->value, &mp_type_staticmethod)) { + // return just the function + dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun; + } else if (MP_OBJ_IS_TYPE(elem->value, &mp_type_classmethod)) { + // return a bound method, with self being the type of this object + dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun; + dest[1] = mp_obj_get_type(base); + } else if (mp_obj_is_callable(elem->value)) { + // return a bound method, with self being this object + dest[0] = elem->value; + dest[1] = base; + } else { + // class member is a value, so just return that value + dest[0] = elem->value; } } } diff --git a/py/runtime.h b/py/runtime.h index b817d61aec..b233d23b4a 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -46,6 +46,7 @@ void mp_unpack_sequence(mp_obj_t seq, uint num, mp_obj_t *items); mp_obj_t mp_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value); mp_obj_t mp_load_attr(mp_obj_t base, qstr attr); void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest); +void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest); void mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t val); void mp_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t val); diff --git a/tests/basics/getattr.py b/tests/basics/getattr.py new file mode 100644 index 0000000000..a021e38fb0 --- /dev/null +++ b/tests/basics/getattr.py @@ -0,0 +1,11 @@ +# test __getattr__ + +class A: + def __init__(self, d): + self.d = d + + def __getattr__(self, attr): + return self.d[attr] + +a = A({'a':1, 'b':2}) +print(a.a, a.b)