py: Implement staticmethod and classmethod (internally).

Still need to make built-ins by these names, and write tests.
This commit is contained in:
Damien George 2014-01-11 19:22:29 +00:00
parent bcbeea0a47
commit eae16445d5
5 changed files with 144 additions and 95 deletions

View File

@ -231,7 +231,7 @@ uint mp_get_index(const mp_obj_type_t *type, machine_uint_t len, mp_obj_t index)
}
}
// may return NULL
// may return MP_OBJ_NULL
mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) {
mp_small_int_t len = 0;
if (MP_OBJ_IS_TYPE(o_in, &str_type)) {
@ -249,7 +249,7 @@ mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) {
} else if (MP_OBJ_IS_TYPE(o_in, &dict_type)) {
len = mp_obj_dict_len(o_in);
} else {
return NULL;
return MP_OBJ_NULL;
}
return MP_OBJ_NEW_SMALL_INT(len);
}

View File

@ -59,6 +59,15 @@ typedef struct _mp_obj_base_t mp_obj_base_t;
#define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) MP_DEFINE_CONST_FUN_OBJ_VOID_PTR(obj_name, false, n_args_min, n_args_max, (mp_fun_var_t)fun_name)
#define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, fun_name) MP_DEFINE_CONST_FUN_OBJ_VOID_PTR(obj_name, true, 0, (~((machine_uint_t)0)), (mp_fun_kw_t)fun_name)
// These macros are used to declare and define constant staticmethond and classmethod objects
// You can put "static" in front of the definitions to make them local
#define MP_DECLARE_CONST_STATICMETHOD_OBJ(obj_name) extern const mp_obj_staticmethod_t obj_name
#define MP_DECLARE_CONST_CLASSMETHOD_OBJ(obj_name) extern const mp_obj_classmethod_t obj_name
#define MP_DEFINE_CONST_STATICMETHOD_OBJ(obj_name, fun_name) const mp_obj_staticmethod_t obj_name = {{&mp_type_staticmethod}, fun_name}
#define MP_DEFINE_CONST_CLASSMETHOD_OBJ(obj_name, fun_name) const mp_obj_classmethod_t obj_name = {{&mp_type_classmethod}, fun_name}
// Need to declare this here so we are not dependent on map.h
struct _mp_map_t;
struct _mp_map_elem_t;
@ -316,3 +325,18 @@ extern const mp_obj_type_t gen_instance_type;
extern const mp_obj_type_t module_type;
mp_obj_t mp_obj_new_module(qstr module_name);
struct _mp_map_t *mp_obj_module_get_globals(mp_obj_t self_in);
// staticmethod and classmethod types; defined here so we can make const versions
extern const mp_obj_type_t mp_type_staticmethod;
extern const mp_obj_type_t mp_type_classmethod;
typedef struct _mp_obj_staticmethod_t {
mp_obj_base_t base;
mp_obj_t fun;
} mp_obj_staticmethod_t;
typedef struct _mp_obj_classmethod_t {
mp_obj_base_t base;
mp_obj_t fun;
} mp_obj_classmethod_t;

View File

@ -139,6 +139,35 @@ static mp_obj_t dict_copy(mp_obj_t self_in) {
}
static MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, dict_copy);
// this is a classmethod
static mp_obj_t dict_fromkeys(int n_args, const mp_obj_t *args) {
assert(2 <= n_args && n_args <= 3);
mp_obj_t iter = rt_getiter(args[1]);
mp_obj_t len = mp_obj_len_maybe(iter);
mp_obj_t value = mp_const_none;
mp_obj_t next = NULL;
mp_obj_dict_t *self = NULL;
if (n_args > 2) {
value = args[2];
}
if (len == MP_OBJ_NULL) {
/* object's type doesn't have a __len__ slot */
self = mp_obj_new_dict(0);
} else {
self = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len));
}
while ((next = rt_iternext(iter)) != mp_const_stop_iteration) {
mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
}
return self;
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_fun_obj, 2, 3, dict_fromkeys);
static MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, (const mp_obj_t)&dict_fromkeys_fun_obj);
static mp_obj_t dict_get_helper(mp_map_t *self, mp_obj_t key, mp_obj_t deflt, mp_map_lookup_kind_t lookup_kind) {
mp_map_elem_t *elem = mp_map_lookup(self, key, lookup_kind);
mp_obj_t value;
@ -280,23 +309,18 @@ static mp_obj_t dict_view_it_iternext(mp_obj_t self_in) {
if (next != NULL) {
switch (self->kind) {
case MP_DICT_VIEW_ITEMS:
{
mp_obj_t items[] = {next->key, next->value};
return mp_obj_new_tuple(2, items);
}
case MP_DICT_VIEW_KEYS:
{
return next->key;
}
case MP_DICT_VIEW_VALUES:
{
return next->value;
}
default:
{
assert(0); /* can't happen */
}
case MP_DICT_VIEW_ITEMS:
{
mp_obj_t items[] = {next->key, next->value};
return mp_obj_new_tuple(2, items);
}
case MP_DICT_VIEW_KEYS:
return next->key;
case MP_DICT_VIEW_VALUES:
return next->value;
default:
assert(0); /* can't happen */
return mp_const_none;
}
} else {
return mp_const_stop_iteration;
@ -320,7 +344,6 @@ static mp_obj_t dict_view_getiter(mp_obj_t view_in) {
return o;
}
static void dict_view_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
assert(MP_OBJ_IS_TYPE(self_in, &dict_view_type));
mp_obj_dict_view_t *self = self_in;
@ -354,7 +377,6 @@ mp_obj_t mp_obj_new_dict_view(mp_obj_dict_t *dict, mp_dict_view_kind_t kind) {
return o;
}
static mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) {
assert(MP_OBJ_IS_TYPE(self_in, &dict_type));
mp_obj_dict_t *self = self_in;
@ -376,67 +398,13 @@ static mp_obj_t dict_values(mp_obj_t self_in) {
}
static MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values);
/******************************************************************************/
/* dict metaclass */
static mp_obj_t dict_fromkeys(int n_args, const mp_obj_t *args) {
assert(2 <= n_args && n_args <= 3);
mp_obj_t iter = rt_getiter(args[1]);
mp_obj_t len = mp_obj_len_maybe(iter);
mp_obj_t value = mp_const_none;
mp_obj_t next = NULL;
mp_obj_dict_t *self = NULL;
if (n_args > 2) {
value = args[2];
}
if (len == NULL) {
/* object's type doesn't have a __len__ slot */
self = mp_obj_new_dict(0);
} else {
self = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len));
}
while ((next = rt_iternext(iter)) != mp_const_stop_iteration) {
mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
}
return self;
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_obj, 2, 3, dict_fromkeys);
static const mp_method_t dict_class_methods[] = {
{ "fromkeys", &dict_fromkeys_obj },
{ NULL, NULL }, // end-of-list sentinel
};
/* this should be unnecessary when inheritance works */
static void dict_class_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
print(env, "<class 'dict'>");
}
/* this should be unnecessary when inheritance works */
static mp_obj_t dict_class_call_n(mp_obj_t self_in, int n_args, const mp_obj_t *args) {
return rt_build_map(0);
}
static const mp_obj_type_t dict_class = {
{ &mp_const_type },
"dict_class",
.print = dict_class_print,
.methods = dict_class_methods,
.call_n = dict_class_call_n,
};
/******************************************************************************/
/* dict constructors & etc */
/* dict constructors & public C API */
static const mp_method_t dict_type_methods[] = {
{ "clear", &dict_clear_obj },
{ "copy", &dict_copy_obj },
{ "fromkeys", &dict_fromkeys_obj },
{ "get", &dict_get_obj },
{ "items", &dict_items_obj },
{ "keys", &dict_keys_obj },
@ -449,7 +417,7 @@ static const mp_method_t dict_type_methods[] = {
};
const mp_obj_type_t dict_type = {
{ &dict_class },
{ &mp_const_type },
"dict",
.print = dict_print,
.make_new = dict_make_new,

View File

@ -27,15 +27,13 @@ static mp_obj_t mp_obj_new_class(mp_obj_t class) {
return o;
}
static mp_map_elem_t *mp_obj_class_lookup(mp_obj_t self_in, qstr attr, mp_map_lookup_kind_t lookup_kind) {
static mp_map_elem_t *mp_obj_class_lookup(const mp_obj_type_t *type, qstr attr, mp_map_lookup_kind_t lookup_kind) {
for (;;) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
mp_obj_type_t *self = self_in;
if (self->locals_dict == NULL) {
if (type->locals_dict == NULL) {
return NULL;
}
assert(MP_OBJ_IS_TYPE(self->locals_dict, &dict_type)); // Micro Python restriction, for now
mp_map_t *locals_map = ((void*)self->locals_dict + sizeof(mp_obj_base_t)); // XXX hack to get map object from dict object
assert(MP_OBJ_IS_TYPE(type->locals_dict, &dict_type)); // Micro Python restriction, for now
mp_map_t *locals_map = ((void*)type->locals_dict + sizeof(mp_obj_base_t)); // XXX hack to get map object from dict object
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), lookup_kind);
if (elem != NULL) {
return elem;
@ -44,25 +42,27 @@ static mp_map_elem_t *mp_obj_class_lookup(mp_obj_t self_in, qstr attr, mp_map_lo
// attribute not found, keep searching base classes
// for a const struct, this entry might be NULL
if (self->bases_tuple == MP_OBJ_NULL) {
if (type->bases_tuple == MP_OBJ_NULL) {
return NULL;
}
uint len;
mp_obj_t *items;
mp_obj_tuple_get(self->bases_tuple, &len, &items);
mp_obj_tuple_get(type->bases_tuple, &len, &items);
if (len == 0) {
return NULL;
}
for (uint i = 0; i < len - 1; i++) {
elem = mp_obj_class_lookup(items[i], attr, lookup_kind);
assert(MP_OBJ_IS_TYPE(items[i], &mp_const_type));
elem = mp_obj_class_lookup((mp_obj_type_t*)items[i], attr, lookup_kind);
if (elem != NULL) {
return elem;
}
}
// search last base (simple tail recursion elimination)
self_in = items[len - 1];
assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_const_type));
type = (mp_obj_type_t*)items[len - 1];
}
}
@ -73,11 +73,12 @@ static void class_print(void (*print)(void *env, const char *fmt, ...), void *en
// args are reverse in the array
static mp_obj_t class_make_new(mp_obj_t self_in, int n_args, const mp_obj_t *args) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
mp_obj_type_t *self = self_in;
mp_obj_t o = mp_obj_new_class(self_in);
// look for __init__ function
mp_map_elem_t *init_fn = mp_obj_class_lookup(self_in, MP_QSTR___init__, MP_MAP_LOOKUP);
mp_map_elem_t *init_fn = mp_obj_class_lookup(self, MP_QSTR___init__, MP_MAP_LOOKUP);
if (init_fn != NULL) {
// call __init__ function
@ -114,7 +115,7 @@ static void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
dest[1] = elem->value;
return;
}
elem = mp_obj_class_lookup((mp_obj_t)self->base.type, attr, MP_MAP_LOOKUP);
elem = mp_obj_class_lookup(self->base.type, attr, MP_MAP_LOOKUP);
if (elem != NULL) {
if (mp_obj_is_callable(elem->value)) {
// class member is callable so build a bound method
@ -132,7 +133,7 @@ static void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
static bool class_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
// logic: look in class locals (no add) then obj members (add) (TODO check this against CPython)
mp_obj_class_t *self = self_in;
mp_map_elem_t *elem = mp_obj_class_lookup((mp_obj_t)self->base.type, attr, MP_MAP_LOOKUP);
mp_map_elem_t *elem = mp_obj_class_lookup(self->base.type, attr, MP_MAP_LOOKUP);
if (elem != NULL) {
elem->value = value;
} else {
@ -188,17 +189,47 @@ static mp_obj_t type_call_n(mp_obj_t self_in, int n_args, const mp_obj_t *args)
// for fail, do nothing; for attr, dest[1] = value; for method, dest[0] = self, dest[1] = method
static void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_map_elem_t *elem = mp_obj_class_lookup(self_in, attr, MP_MAP_LOOKUP);
assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
mp_obj_type_t *self = self_in;
mp_map_elem_t *elem = mp_obj_class_lookup(self, attr, MP_MAP_LOOKUP);
if (elem != NULL) {
dest[1] = elem->value;
return;
}
// generic method lookup
// this is a lookup in the class itself (ie not the classes type or instance)
const mp_method_t *meth = self->methods;
if (meth != NULL) {
for (; meth->name != NULL; meth++) {
if (strcmp(meth->name, qstr_str(attr)) == 0) {
// 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(meth->fun, &mp_type_staticmethod)) {
// return just the function
dest[1] = ((mp_obj_staticmethod_t*)meth->fun)->fun;
} else if (MP_OBJ_IS_TYPE(meth->fun, &mp_type_classmethod)) {
// return a bound method, with self being this class
dest[1] = ((mp_obj_classmethod_t*)meth->fun)->fun;
dest[0] = self_in;
} else {
// return just the function
// TODO need to wrap in a type check for the first argument; eg list.append(1,1) needs to throw an exception
dest[1] = (mp_obj_t)meth->fun;
}
return;
}
}
}
}
static bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
mp_obj_type_t *self = self_in;
// TODO CPython allows STORE_ATTR to a class, but is this the correct implementation?
mp_map_elem_t *elem = mp_obj_class_lookup(self_in, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
mp_map_elem_t *elem = mp_obj_class_lookup(self, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
if (elem != NULL) {
elem->value = value;
return true;
@ -284,3 +315,16 @@ static mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) {
}
MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance);
/******************************************************************************/
// staticmethod and classmethod types (probably should go in a different file)
const mp_obj_type_t mp_type_staticmethod = {
{ &mp_const_type },
"staticmethod",
};
const mp_obj_type_t mp_type_classmethod = {
{ &mp_const_type },
"classmethod",
};

View File

@ -774,12 +774,25 @@ void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
dest[0] = base;
} else {
// generic method lookup
// this is a lookup in the object (ie not class or type)
const mp_method_t *meth = type->methods;
if (meth != NULL) {
for (; meth->name != NULL; meth++) {
if (strcmp(meth->name, qstr_str(attr)) == 0) {
dest[1] = (mp_obj_t)meth->fun;
dest[0] = base;
// 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(meth->fun, &mp_type_staticmethod)) {
// return just the function
dest[1] = ((mp_obj_staticmethod_t*)meth->fun)->fun;
} else if (MP_OBJ_IS_TYPE(meth->fun, &mp_type_classmethod)) {
// return a bound method, with self being the type of this object
dest[1] = ((mp_obj_classmethod_t*)meth->fun)->fun;
dest[0] = mp_obj_get_type(base);
} else {
// return a bound method, with self being this object
dest[1] = (mp_obj_t)meth->fun;
dest[0] = base;
}
break;
}
}