Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

py: Support object __getattr__, __delattr__ and __setattr__ methods #2687

Closed
wants to merge 9 commits into from
6 changes: 6 additions & 0 deletions py/mpconfig.h
Expand Up @@ -636,6 +636,12 @@ typedef double mp_float_t;
#define MICROPY_PY_DESCRIPTORS (0)
#endif

// Whether to support object (__getattr__, __delattr__ and __setattr__)
// This costs some code size and makes all load attrs and store attrs slow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'slower' would be better wording imo, without measuring the actual impact it's hard to make absolute statements about it.

#ifndef MICROPY_PY_ATTRS_METHODS
#define MICROPY_PY_ATTRS_METHODS (0)
#endif

// Support for async/await/async for/async with
#ifndef MICROPY_PY_ASYNC_AWAIT
#define MICROPY_PY_ASYNC_AWAIT (1)
Expand Down
35 changes: 35 additions & 0 deletions py/objobject.c
Expand Up @@ -59,13 +59,48 @@ STATIC mp_obj_t object___new__(mp_obj_t cls) {
STATIC MP_DEFINE_CONST_FUN_OBJ_1(object___new___fun_obj, object___new__);
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(object___new___obj, MP_ROM_PTR(&object___new___fun_obj));

#if MICROPY_PY_ATTRS_METHODS
STATIC mp_obj_t object___setattr__(mp_obj_t self_in, mp_obj_t attr_in, mp_obj_t value) {
mp_map_lookup(mp_obj_dict_get_map(self_in), MP_OBJ_NEW_QSTR(mp_obj_str_get_qstr(attr_in)), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
return value;
}
MP_DEFINE_CONST_FUN_OBJ_3(object___setattr___obj, object___setattr__);

STATIC mp_obj_t object___getattr__(mp_obj_t self_in, mp_obj_t attr_in) {
qstr attr = mp_obj_str_get_qstr(attr_in);
mp_map_elem_t *elem = mp_map_lookup(mp_obj_dict_get_map(self_in), MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem == NULL) {
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_AttributeError,
"'%s' object has no attribute '%q'",
mp_obj_get_type_str(self_in), attr));
}
return elem->value;
}
MP_DEFINE_CONST_FUN_OBJ_2(object___getattr___obj, object___getattr__);

STATIC mp_obj_t object___delattr__(mp_obj_t self_in, mp_obj_t attr_in) {
mp_map_lookup(mp_obj_dict_get_map(self_in), MP_OBJ_NEW_QSTR(mp_obj_str_get_qstr(attr_in)), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(object___delattr___obj, object___delattr__);
#endif

STATIC const mp_rom_map_elem_t object_locals_dict_table[] = {
#if MICROPY_CPYTHON_COMPAT
{ MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&object___init___obj) },
#endif
#if MICROPY_CPYTHON_COMPAT
{ MP_ROM_QSTR(MP_QSTR___new__), MP_ROM_PTR(&object___new___obj) },
#endif
#if (MICROPY_CPYTHON_COMPAT && MICROPY_PY_ATTRS_METHODS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just #if MICROPY_PY_ATTRS_METHODS ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

becouse i have placed it into:

#if MICROPY_CPYTHON_COMPAT
....
#endif

Copy link
Contributor

@stinos stinos Dec 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah didin't see that in the github diff. Still it's a bit confusing as now one might use #define MICROPY_PY_ATTRS_METHODS 1 but it won't have any effect unless MICROPY_CPYTHON_COMPAT is also defined. But maybe there's no other way. Though actually this could use some cleanup: everything is already wrapped in #if MICROPY_CPYTHON_COMPAT starting at line 44 so I don't immediately see why it needs duplication.

{ MP_ROM_QSTR(MP_QSTR___delattr__), MP_ROM_PTR(&object___delattr___obj) },
#endif
#if (MICROPY_CPYTHON_COMPAT && MICROPY_PY_ATTRS_METHODS)
{ MP_ROM_QSTR(MP_QSTR___getattr__), MP_ROM_PTR(&object___getattr___obj) },
#endif
#if (MICROPY_CPYTHON_COMPAT && MICROPY_PY_ATTRS_METHODS)
{ MP_ROM_QSTR(MP_QSTR___setattr__), MP_ROM_PTR(&object___setattr___obj) },
#endif
};

STATIC MP_DEFINE_CONST_DICT(object_locals_dict, object_locals_dict_table);
Expand Down
29 changes: 29 additions & 0 deletions py/objtype.c
Expand Up @@ -626,10 +626,39 @@ STATIC bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t val

if (value == MP_OBJ_NULL) {
// delete attribute
#if MICROPY_PY_ATTRS_METHODS
// try __delattr__ first
if (attr != MP_QSTR___delattr__) {
mp_obj_t attr_delattr_method[3];
mp_load_method_maybe(self_in, MP_QSTR___delattr__, attr_delattr_method);
if (attr_delattr_method[0] != MP_OBJ_NULL) {
// __delattr__ exists, call it and return its result
attr_delattr_method[2] = MP_OBJ_NEW_QSTR(attr);
mp_call_method_n_kw(1, 0, attr_delattr_method);
return true;
}
}
#endif

mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
return elem != NULL;
} else {
// store attribute
#if MICROPY_PY_ATTRS_METHODS
// try __setattr__ first
if (attr != MP_QSTR___setattr__) {
mp_obj_t attr_setattr_method[4];
mp_load_method_maybe(self_in, MP_QSTR___setattr__, attr_setattr_method);
if (attr_setattr_method[0] != MP_OBJ_NULL) {
// __setattr__ exists, call it and return its result
attr_setattr_method[2] = MP_OBJ_NEW_QSTR(attr);
attr_setattr_method[3] = value;
mp_call_method_n_kw(2, 0, attr_setattr_method);
return true;
}
}
#endif

mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
return true;
}
Expand Down