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.
This commit is contained in:
parent
4db727afea
commit
e44d26ae0c
14
py/objtype.c
14
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;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ Q(__add__)
|
||||
Q(__sub__)
|
||||
Q(__repr__)
|
||||
Q(__str__)
|
||||
Q(__getattr__)
|
||||
|
||||
Q(micropython)
|
||||
Q(byte_code)
|
||||
|
79
py/runtime.c
79
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
11
tests/basics/getattr.py
Normal file
11
tests/basics/getattr.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user