Skip to content

Commit e44d26a

Browse files
committed
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.
1 parent 4db727a commit e44d26a

5 files changed

Lines changed: 65 additions & 41 deletions

File tree

py/objtype.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
typedef struct _mp_obj_class_t {
1717
mp_obj_base_t base;
1818
mp_map_t members;
19+
// TODO maybe cache __getattr__ and __setattr__ for efficient lookup of them
1920
} mp_obj_class_t;
2021

2122
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) {
225226
} else {
226227
// class member is a value, so just return that value
227228
dest[0] = member;
229+
}
230+
return;
231+
}
232+
233+
// try __getattr__
234+
if (attr != MP_QSTR___getattr__) {
235+
mp_obj_t dest2[3];
236+
mp_load_method_maybe(self_in, MP_QSTR___getattr__, dest2);
237+
if (dest2[0] != MP_OBJ_NULL) {
238+
// __getattr__ exists, call it and return its result
239+
// XXX if this fails to load the requested attr, should we catch the attribute error and return silently?
240+
dest2[2] = MP_OBJ_NEW_QSTR(attr);
241+
dest[0] = mp_call_method_n_kw(1, 0, dest2);
228242
return;
229243
}
230244
}

py/qstrdefs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Q(__add__)
2626
Q(__sub__)
2727
Q(__repr__)
2828
Q(__str__)
29+
Q(__getattr__)
2930

3031
Q(micropython)
3132
Q(byte_code)

py/runtime.c

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -701,56 +701,53 @@ mp_obj_t mp_load_attr(mp_obj_t base, qstr attr) {
701701
// no attribute found, returns: dest[0] == MP_OBJ_NULL, dest[1] == MP_OBJ_NULL
702702
// normal attribute found, returns: dest[0] == <attribute>, dest[1] == MP_OBJ_NULL
703703
// method attribute found, returns: dest[0] == <method>, dest[1] == <self>
704-
STATIC void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest) {
704+
void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest) {
705705
// clear output to indicate no attribute/method found yet
706706
dest[0] = MP_OBJ_NULL;
707707
dest[1] = MP_OBJ_NULL;
708708

709709
// get the type
710710
mp_obj_type_t *type = mp_obj_get_type(base);
711711

712-
// if this type can do its own load, then call it
713-
if (type->load_attr != NULL) {
714-
type->load_attr(base, attr, dest);
715-
}
716-
717-
// if nothing found yet, look for built-in and generic names
718-
if (dest[0] == MP_OBJ_NULL) {
712+
// look for built-in names
713+
if (0) {
719714
#if MICROPY_CPYTHON_COMPAT
720-
if (attr == MP_QSTR___class__) {
721-
// a.__class__ is equivalent to type(a)
722-
dest[0] = type;
723-
} else
715+
} else if (attr == MP_QSTR___class__) {
716+
// a.__class__ is equivalent to type(a)
717+
dest[0] = type;
724718
#endif
725-
if (attr == MP_QSTR___next__ && type->iternext != NULL) {
726-
dest[0] = (mp_obj_t)&mp_builtin_next_obj;
727-
dest[1] = base;
728-
} else if (type->load_attr == NULL) {
729-
// generic method lookup if type didn't provide a specific one
730-
// this is a lookup in the object (ie not class or type)
731-
if (type->locals_dict != NULL) {
732-
assert(MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)); // Micro Python restriction, for now
733-
mp_map_t *locals_map = mp_obj_dict_get_map(type->locals_dict);
734-
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
735-
if (elem != NULL) {
736-
// check if the methods are functions, static or class methods
737-
// see http://docs.python.org/3.3/howto/descriptor.html
738-
if (MP_OBJ_IS_TYPE(elem->value, &mp_type_staticmethod)) {
739-
// return just the function
740-
dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun;
741-
} else if (MP_OBJ_IS_TYPE(elem->value, &mp_type_classmethod)) {
742-
// return a bound method, with self being the type of this object
743-
dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun;
744-
dest[1] = mp_obj_get_type(base);
745-
} else if (mp_obj_is_callable(elem->value)) {
746-
// return a bound method, with self being this object
747-
dest[0] = elem->value;
748-
dest[1] = base;
749-
} else {
750-
// class member is a value, so just return that value
751-
dest[0] = elem->value;
752-
}
753-
}
719+
720+
} else if (attr == MP_QSTR___next__ && type->iternext != NULL) {
721+
dest[0] = (mp_obj_t)&mp_builtin_next_obj;
722+
dest[1] = base;
723+
724+
} else if (type->load_attr != NULL) {
725+
// this type can do its own load, so call it
726+
type->load_attr(base, attr, dest);
727+
728+
} else if (type->locals_dict != NULL) {
729+
// generic method lookup
730+
// this is a lookup in the object (ie not class or type)
731+
assert(MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)); // Micro Python restriction, for now
732+
mp_map_t *locals_map = mp_obj_dict_get_map(type->locals_dict);
733+
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
734+
if (elem != NULL) {
735+
// check if the methods are functions, static or class methods
736+
// see http://docs.python.org/3.3/howto/descriptor.html
737+
if (MP_OBJ_IS_TYPE(elem->value, &mp_type_staticmethod)) {
738+
// return just the function
739+
dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun;
740+
} else if (MP_OBJ_IS_TYPE(elem->value, &mp_type_classmethod)) {
741+
// return a bound method, with self being the type of this object
742+
dest[0] = ((mp_obj_static_class_method_t*)elem->value)->fun;
743+
dest[1] = mp_obj_get_type(base);
744+
} else if (mp_obj_is_callable(elem->value)) {
745+
// return a bound method, with self being this object
746+
dest[0] = elem->value;
747+
dest[1] = base;
748+
} else {
749+
// class member is a value, so just return that value
750+
dest[0] = elem->value;
754751
}
755752
}
756753
}

py/runtime.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ void mp_unpack_sequence(mp_obj_t seq, uint num, mp_obj_t *items);
4646
mp_obj_t mp_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value);
4747
mp_obj_t mp_load_attr(mp_obj_t base, qstr attr);
4848
void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest);
49+
void mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest);
4950
void mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t val);
5051
void mp_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t val);
5152

tests/basics/getattr.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# test __getattr__
2+
3+
class A:
4+
def __init__(self, d):
5+
self.d = d
6+
7+
def __getattr__(self, attr):
8+
return self.d[attr]
9+
10+
a = A({'a':1, 'b':2})
11+
print(a.a, a.b)

0 commit comments

Comments
 (0)