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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy