mirror of
https://github.com/micropython/micropython.git
synced 2025-07-21 21:11:12 +02:00
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>
This commit is contained in:
@@ -1041,6 +1041,11 @@ typedef double mp_float_t;
|
|||||||
#define MICROPY_PY_FUNCTION_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
|
#define MICROPY_PY_FUNCTION_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Whether to implement the __code__ attribute on functions, and function constructor
|
||||||
|
#ifndef MICROPY_PY_FUNCTION_ATTRS_CODE
|
||||||
|
#define MICROPY_PY_FUNCTION_ATTRS_CODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Whether to support the descriptors __get__, __set__, __delete__
|
// Whether to support the descriptors __get__, __set__, __delete__
|
||||||
// This costs some code size and makes load/store/delete of instance
|
// This costs some code size and makes load/store/delete of instance
|
||||||
// attributes slower for the classes that use this feature
|
// attributes slower for the classes that use this feature
|
||||||
@@ -1135,7 +1140,7 @@ typedef double mp_float_t;
|
|||||||
#define MICROPY_PY_BUILTINS_CODE_BASIC (2)
|
#define MICROPY_PY_BUILTINS_CODE_BASIC (2)
|
||||||
#define MICROPY_PY_BUILTINS_CODE_FULL (3)
|
#define MICROPY_PY_BUILTINS_CODE_FULL (3)
|
||||||
#ifndef MICROPY_PY_BUILTINS_CODE
|
#ifndef MICROPY_PY_BUILTINS_CODE
|
||||||
#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))
|
#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)))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Whether to support dict.fromkeys() class method
|
// Whether to support dict.fromkeys() class method
|
||||||
|
47
py/objfun.c
47
py/objfun.c
@@ -28,6 +28,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "py/emitglue.h"
|
||||||
|
#include "py/objcode.h"
|
||||||
#include "py/objtuple.h"
|
#include "py/objtuple.h"
|
||||||
#include "py/objfun.h"
|
#include "py/objfun.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
@@ -151,6 +153,30 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MICROPY_PY_FUNCTION_ATTRS_CODE
|
||||||
|
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) {
|
||||||
|
(void)type;
|
||||||
|
mp_arg_check_num(n_args, n_kw, 2, 2, false);
|
||||||
|
|
||||||
|
if (!mp_obj_is_type(args[0], &mp_type_code)) {
|
||||||
|
mp_raise_TypeError(NULL);
|
||||||
|
}
|
||||||
|
if (!mp_obj_is_type(args[1], &mp_type_dict)) {
|
||||||
|
mp_raise_TypeError(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
mp_obj_code_t *code = MP_OBJ_TO_PTR(args[0]);
|
||||||
|
mp_obj_t globals = args[1];
|
||||||
|
|
||||||
|
mp_module_context_t *module_context = m_new_obj(mp_module_context_t);
|
||||||
|
module_context->module.base.type = &mp_type_module;
|
||||||
|
module_context->module.globals = MP_OBJ_TO_PTR(globals);
|
||||||
|
module_context->constants = *mp_code_get_constants(code);
|
||||||
|
|
||||||
|
return mp_make_function_from_proto_fun(mp_code_get_proto_fun(code), module_context, NULL);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if MICROPY_CPYTHON_COMPAT
|
#if MICROPY_CPYTHON_COMPAT
|
||||||
static void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
|
static void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
|
||||||
(void)kind;
|
(void)kind;
|
||||||
@@ -340,9 +366,29 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
|
|||||||
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
|
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
|
||||||
dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals);
|
dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals);
|
||||||
}
|
}
|
||||||
|
#if MICROPY_PY_FUNCTION_ATTRS_CODE
|
||||||
|
if (attr == MP_QSTR___code__) {
|
||||||
|
const mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
|
||||||
|
if ((self->base.type == &mp_type_fun_bc
|
||||||
|
|| self->base.type == &mp_type_gen_wrap)
|
||||||
|
&& self->child_table == NULL) {
|
||||||
|
#if MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC
|
||||||
|
dest[0] = mp_obj_new_code(self->context->constants, self->bytecode);
|
||||||
|
#else
|
||||||
|
dest[0] = mp_obj_new_code(self->context, self->rc, true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if MICROPY_PY_FUNCTION_ATTRS_CODE
|
||||||
|
#define FUN_BC_MAKE_NEW make_new, fun_bc_make_new,
|
||||||
|
#else
|
||||||
|
#define FUN_BC_MAKE_NEW
|
||||||
|
#endif
|
||||||
|
|
||||||
#if MICROPY_CPYTHON_COMPAT
|
#if MICROPY_CPYTHON_COMPAT
|
||||||
#define FUN_BC_TYPE_PRINT print, fun_bc_print,
|
#define FUN_BC_TYPE_PRINT print, fun_bc_print,
|
||||||
#else
|
#else
|
||||||
@@ -359,6 +405,7 @@ MP_DEFINE_CONST_OBJ_TYPE(
|
|||||||
mp_type_fun_bc,
|
mp_type_fun_bc,
|
||||||
MP_QSTR_function,
|
MP_QSTR_function,
|
||||||
MP_TYPE_FLAG_BINDS_SELF,
|
MP_TYPE_FLAG_BINDS_SELF,
|
||||||
|
FUN_BC_MAKE_NEW
|
||||||
FUN_BC_TYPE_PRINT
|
FUN_BC_TYPE_PRINT
|
||||||
FUN_BC_TYPE_ATTR
|
FUN_BC_TYPE_ATTR
|
||||||
call, fun_bc_call
|
call, fun_bc_call
|
||||||
|
36
tests/basics/fun_code.py
Normal file
36
tests/basics/fun_code.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Test function.__code__ attribute.
|
||||||
|
|
||||||
|
try:
|
||||||
|
(lambda: 0).__code__
|
||||||
|
except AttributeError:
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
ftype = type(f)
|
||||||
|
|
||||||
|
# Test __code__ access and function constructor.
|
||||||
|
code = f.__code__
|
||||||
|
print(type(ftype(code, {})) is ftype)
|
||||||
|
|
||||||
|
# Test instantiating multiple code's with different globals dicts.
|
||||||
|
code = f.__code__
|
||||||
|
f1 = ftype(code, {"a": 1})
|
||||||
|
f2 = ftype(code, {"a": 2})
|
||||||
|
print(f1(), f2())
|
||||||
|
|
||||||
|
# Test bad first argument type.
|
||||||
|
try:
|
||||||
|
ftype(None, {})
|
||||||
|
except TypeError:
|
||||||
|
print("TypeError")
|
||||||
|
|
||||||
|
# Test bad second argument type.
|
||||||
|
try:
|
||||||
|
ftype(f.__code__, None)
|
||||||
|
except TypeError:
|
||||||
|
print("TypeError")
|
19
tests/basics/fun_code_micropython.py
Normal file
19
tests/basics/fun_code_micropython.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Test MicroPython-specific restrictions of function.__code__ attribute.
|
||||||
|
|
||||||
|
try:
|
||||||
|
(lambda: 0).__code__
|
||||||
|
except AttributeError:
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
def f_with_children():
|
||||||
|
def g():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Can't access __code__ when function has children.
|
||||||
|
try:
|
||||||
|
f_with_children.__code__
|
||||||
|
except AttributeError:
|
||||||
|
print("AttributeError")
|
1
tests/basics/fun_code_micropython.py.exp
Normal file
1
tests/basics/fun_code_micropython.py.exp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AttributeError
|
@@ -21,11 +21,9 @@ print(a + [20, 30, 40])
|
|||||||
# TODO: Faults
|
# TODO: Faults
|
||||||
#print(a + a)
|
#print(a + a)
|
||||||
|
|
||||||
def foo():
|
# subclassing a type that doesn't have make_new at the C level (not allowed)
|
||||||
print("hello from foo")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
class myfunc(type(foo)):
|
class myfunc(type([].append)):
|
||||||
pass
|
pass
|
||||||
except TypeError:
|
except TypeError:
|
||||||
print("TypeError")
|
print("TypeError")
|
||||||
|
21
tests/micropython/native_fun_attrs_code.py
Normal file
21
tests/micropython/native_fun_attrs_code.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Test MicroPython-specific restrictions of function.__code__ attribute.
|
||||||
|
|
||||||
|
try:
|
||||||
|
(lambda: 0).__code__
|
||||||
|
except AttributeError:
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
import micropython
|
||||||
|
|
||||||
|
|
||||||
|
@micropython.native
|
||||||
|
def f_native():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Can't access __code__ when function is native code.
|
||||||
|
try:
|
||||||
|
f_native.__code__
|
||||||
|
except AttributeError:
|
||||||
|
print("AttributeError")
|
1
tests/micropython/native_fun_attrs_code.py.exp
Normal file
1
tests/micropython/native_fun_attrs_code.py.exp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AttributeError
|
Reference in New Issue
Block a user