From 07ccb5588c4abcffa28f25907e699d1727d38bae Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 9 Dec 2019 18:31:35 +0200 Subject: [PATCH] py/objobject: Add object.__setattr__ function. Allows assigning attributes on class instances that implement their own __setattr__. Both object.__setattr__ and super(A, b).__setattr__ will work with this commit. --- py/objobject.c | 22 +++++++++++++++++++++- py/runtime.c | 4 +++- tests/basics/class_delattr_setattr.py | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/py/objobject.c b/py/objobject.c index 45228347e9..ae8436bc88 100644 --- a/py/objobject.c +++ b/py/objobject.c @@ -50,7 +50,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(object___init___obj, object___init__); STATIC mp_obj_t object___new__(mp_obj_t cls) { if (!mp_obj_is_type(cls, &mp_type_type) || !mp_obj_is_instance_type((mp_obj_type_t*)MP_OBJ_TO_PTR(cls))) { - mp_raise_TypeError("__new__ arg must be a user-type"); + mp_raise_TypeError("arg must be user-type"); } // This executes only "__new__" part of instance creation. // TODO: This won't work well for classes with native bases. @@ -62,6 +62,23 @@ 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_DELATTR_SETATTR +STATIC mp_obj_t object___setattr__(mp_obj_t self_in, mp_obj_t attr, mp_obj_t value) { + if (!mp_obj_is_instance_type(mp_obj_get_type(MP_OBJ_TO_PTR(self_in)))) { + mp_raise_TypeError("arg must be user-type"); + } + + if (!mp_obj_is_str(attr)) { + mp_raise_TypeError(NULL); + } + + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_lookup(&self->members, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(object___setattr___obj, object___setattr__); +#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) }, @@ -69,6 +86,9 @@ STATIC const mp_rom_map_elem_t object_locals_dict_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR___new__), MP_ROM_PTR(&object___new___obj) }, #endif + #if MICROPY_PY_DELATTR_SETATTR + { 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); diff --git a/py/runtime.c b/py/runtime.c index deb82e9355..cf4fc5d38f 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1038,9 +1038,11 @@ void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t || m_type == &mp_type_fun_builtin_1 || m_type == &mp_type_fun_builtin_2 || m_type == &mp_type_fun_builtin_3 - || m_type == &mp_type_fun_builtin_var)) { + || m_type == &mp_type_fun_builtin_var) + && type != &mp_type_object) { // we extracted a builtin method without a first argument, so we must // wrap this function in a type checker + // Note that object will do its own checking so shouldn't be wrapped. dest[0] = mp_obj_new_checked_fun(type, member); } else #endif diff --git a/tests/basics/class_delattr_setattr.py b/tests/basics/class_delattr_setattr.py index 190b4875b9..8fe1bb6fc1 100644 --- a/tests/basics/class_delattr_setattr.py +++ b/tests/basics/class_delattr_setattr.py @@ -60,3 +60,29 @@ try: print(a.a) except AttributeError: print("AttributeError") + +# test object.__setattr__ +class C: + def __init__(self): + pass + + def __setattr__(self, attr, value): + print(attr, "=", value) + +c = C() +c.a = 5 +try: + print(c.a) +except AttributeError: + print("AttributeError") + +object.__setattr__(c, "a", 5) +super(C, c).__setattr__("b", 6) +print(c.a) +print(c.b) + +try: + # attribute name must be string + object.__setattr__(c, 5, 5) +except TypeError: + print("TypeError")