8000 py/objfun: Implement function.__code__ and function constructor. · sparkfun/micropython@ceb8ba6 · GitHub
[go: up one dir, main page]

Skip to content

Commit ceb8ba6

Browse files
committed
py/objfun: Implement function.__code__ and function constructor.
This allows retrieving the code object of a function using `function.__code__`, and then reconstructing a function from a code object using `FunctionType(code_object)`. This feature is controlled by `MICROPY_PY_FUNCTION_ATTRS_CODE` and is enabled at the full-features level. Signed-off-by: Damien George <damien@micropython.org>
1 parent 62e821c commit ceb8ba6

File tree

8 files changed

+133
-5
lines changed

8 files changed

+133
-5
lines changed

py/mpconfig.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,11 @@ typedef double mp_float_t;
10411041
#define MICROPY_PY_FUNCTION_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
10421042
#endif
10431043

1044+
// Whether to implement the __code__ attribute on functions, and function constructor
1045+
#ifndef MICROPY_PY_FUNCTION_ATTRS_CODE
1046+
#define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES)
1047+
#endif
1048+
10441049
// Whether to support the descriptors __get__, __set__, __delete__
10451050
// This costs some code size and makes load/store/delete of instance
10461051
// attributes slower for the classes that use this feature
@@ -1135,7 +1140,7 @@ typedef double mp_float_t;
11351140
#define MICROPY_PY_BUILTINS_CODE_BASIC (2)
11361141
#define MICROPY_PY_BUILTINS_CODE_FULL (3)
11371142
#ifndef MICROPY_PY_BUILTINS_CODE
1138-
#define MICROPY_PY_BUILTINS_CODE (MICROPY_PY_SYS_SETTRACE ? MICROPY_PY_BUILTINS_CODE_FULL : (MICROPY_PY_BUILTINS_COMPILE ? MICROPY_PY_BUILTINS_CODE_MINIMUM : MICROPY_PY_BUILTINS_CODE_NONE))
1143+
#define MICROPY_PY_BUILTINS_CODE (MICROPY_PY_SYS_SETTRACE ? MICROPY_PY_BUILTINS_CODE_FULL : (MICROPY_PY_FUNCTION_ATTRS_CODE ? MICROPY_PY_BUILTINS_CODE_BASIC : (MICROPY_PY_BUILTINS_COMPILE ? MICROPY_PY_BUILTINS_CODE_MINIMUM : MICROPY_PY_BUILTINS_CODE_NONE)))
11391144
#endif
11401145

11411146
// Whether to support dict.fromkeys() class method

py/objfun.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include <string.h>
2929
#include <assert.h>
3030

31+
#include "py/emitglue.h"
32+
#include "py/objcode.h"
3133
#include "py/objtuple.h"
3234
#include "py/objfun.h"
3335
#include "py/runtime.h"
@@ -151,6 +153,30 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) {
151153
return name;
152154
}
153155

156+
#if MICROPY_PY_FUNCTION_ATTRS_CODE
157+
static mp_obj_t fun_bc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
158+
(void)type;
159+
mp_arg_check_num(n_args, n_kw, 2, 2, false);
160+
161+
if (!mp_obj_is_type(args[0], &mp_type_code)) {
162+
mp_raise_TypeError(NULL);
163+
}
164+
if (!mp_obj_is_type(args[1], &mp_type_dict)) {
165+
mp_raise_TypeError(NULL);
166+
}
167+
168+
mp_obj_code_t *code = MP_OBJ_TO_PTR(args[0]);
169+
mp_obj_t globals = args[1];
170+
171+
mp_module_context_t *module_context = m_new_obj(mp_module_context_t);
172+
module_context->module.base.type = &mp_type_module;
173+
module_context->module.globals = MP_OBJ_TO_PTR(globals);
174+
module_context->constants = *mp_code_get_constants(code);
175+
176+
return mp_make_function_from_proto_fun(mp_code_get_proto_fun(code), module_context, NULL);
177+
}
178+
#endif
179+
154180
#if MICROPY_CPYTHON_COMPAT
155181
static void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
156182
(void)kind;
@@ -340,9 +366,29 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
340366
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
341367
dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals);
342368
}
369+
#if MICROPY_PY_FUNCTION_ATTRS_CODE
370+
if (attr == MP_QSTR___code__) {
371+
const mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
372+
if ((self->base.type == &mp_type_fun_bc
373+
|| self->base.type == &mp_type_gen_wrap)
374+
&& self->child_table == NULL) {
375+
#if MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC
376+
dest[0] = mp_obj_new_code(self->context->constants, self->bytecode);
377+
#else
378+
dest[0] = mp_obj_new_code(self->context, self->rc, true);
379+
#endif
380+
}
381+
}
382+
#endif
343383
}
344384
#endif
345385

386+
#if MICROPY_PY_FUNCTION_ATTRS_CODE
387+
#define FUN_BC_MAKE_NEW make_new, fun_bc_make_new,
388+
#else
389+
#define FUN_BC_MAKE_NEW
390+
#endif
391+
346392
#if MICROPY_CPYTHON_COMPAT
347393
#define FUN_BC_TYPE_PRINT print, fun_bc_print,
348394
#else
@@ -359,6 +405,7 @@ MP_DEFINE_CONST_OBJ_TYPE(
359405
mp_type_fun_bc,
360406
MP_QSTR_function,
361407
MP_TYPE_FLAG_BINDS_SELF,
408+
FUN_BC_MAKE_NEW
362409
FUN_BC_TYPE_PRINT
363410
FUN_BC_TYPE_ATTR
364411
call, fun_bc_call

tests/basics/fun_code.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Test function.__code__ attribute.
2+
3+
try:
4+
(lambda: 0).__code__
5+
except AttributeError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
10+
def f():
11+
return a
12+
13+
14+
ftype = type(f)
15+
16+
# Test __code__ access and function constructor.
17+
code = f.__code__
18+
print(type(ftype(code, {})) is ftype)
19+
20+
# Test instantiating multiple code's with different globals dicts.
21+
code = f.__code__
22+
f1 = ftype(code, {"a": 1})
23+
f2 = ftype(code, {"a": 2})
24+
print(f1(), f2())
25+
26+
# Test bad first argument type.
27+
try:
28+
ftype(None, {})
29+
except TypeError:
30+
print("TypeError")
31+
32+
# Test bad second argument type.
33+
try:
34+
ftype(f.__code__, None)
35+
except TypeError:
36+
print("TypeError")

tests/basics/fun_code_micropython.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Test MicroPython-specific restrictions of function.__code__ attribute.
2+
3+
try:
4+
(lambda: 0).__code__
5+
except AttributeError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
10+
def f_with_children():
11+
def g():
12+
pass
13+
14+
15+
# Can't access __code__ when function has children.
16+
try:
17+
f_with_children.__code__
18+
except AttributeError:
19+
print("AttributeError")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AttributeError

tests/basics/subclass_native1.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ class mylist(list):
2121
# TODO: Faults
2222
#print(a + a)
2323

24-
def foo():
25-
print("hello from foo")
26-
24+
# subclassing a type that doesn't have make_new at the C level (not allowed)
2725
try:
28-
class myfunc(type(foo)):
26+
class myfunc(type([].append)):
2927
pass
3028
except TypeError:
3129
print("TypeError")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Test MicroPython-specific restrictions of function.__code__ attribute.
2+
3+
try:
4+
(lambda: 0).__code__
5+
except AttributeError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
import micropython
10+
11+
12+
@micropython.native
13+
def f_native():
14+
pass
15+
16+
17+
# Can't access __code__ when function is native code.
18+
try:
19+
f_native.__code__
20+
except AttributeError:
21+
print("AttributeError")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AttributeError

0 commit comments

Comments
 (0)
0