summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDamien George <damien.p.george@gmail.com>2014-03-31 22:57:56 +0100
committerDamien George <damien.p.george@gmail.com>2014-03-31 22:57:56 +0100
commite44d26ae0c1b5d248fa4db112cdeabe404944f3c (patch)
tree03bad17caf73555d5880263e894b5dc2fdb5dbe2
parent4db727afea0082780fca558ff251afb4a8b32ad7 (diff)
downloadmicropython-e44d26ae0c1b5d248fa4db112cdeabe404944f3c.tar.gz
micropython-e44d26ae0c1b5d248fa4db112cdeabe404944f3c.zip
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.
-rw-r--r--py/objtype.c14
-rw-r--r--py/qstrdefs.h1
-rw-r--r--py/runtime.c79
-rw-r--r--py/runtime.h1
-rw-r--r--tests/basics/getattr.py11
5 files changed, 65 insertions, 41 deletions
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] == <attribute>, dest[1] == MP_OBJ_NULL
// method attribute found, returns: dest[0] == <method>, dest[1] == <self>
-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)