Zoltán Vörös
5 years ago
1 changed files with 0 additions and 766 deletions
@ -1,766 +0,0 @@ |
|||
{ |
|||
"cells": [ |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"# Introduction\n", |
|||
"\n", |
|||
"Since micropython is a complete python interpreter, it is really easy to add new functionality to the hardware. However, there are cases, when defining the new functionality in python is not going to make the cut. First, the microcontroller might not have enough free RAM to compile the code, and second, the interpreter needs time for the compilation step, therefore, the execution of the instructions is slowed down. One can, in principle, gain on speed by implementing time critical code in assembly, and then invoking the `@viper` function decorator, but that has its own limitations. E.g., the number of positional arguments is maximised, and no keyword arguments are allowed, just to name a few problems.\n", |
|||
"\n", |
|||
"I have been \n", |
|||
"\n", |
|||
"In what follows, I would like to show that adding your own C module is not difficult at all, especially that the micropython code base is organised in a rather logical fashion. \n", |
|||
"\n", |
|||
"I will try to discuss all aspects of micropython in an approachable way. At the end of each chapter, I will present the discussed code in its entirety. You can simply \n", |
|||
"\n", |
|||
"I start out with a very simple module and slowly build upon it. At the end of the discussion, we will create a general-purpose math library, similar to numpy.\n", |
|||
"\n", |
|||
"Obviously, numpy is a huge library, and we are not going to implement all aspects of it. But we will be able to define arrays on which we can do vectorised computations, work with matrices, invert and contract them, fit polynomials to measurement data, and get the Fourier transform of an arbitrary sequence. I hope you will enjoy the ride!\n", |
|||
"\n", |
|||
"One last comment: I believe, all examples in this document could be implemented with little effort in python itself, and I am definitely not advocating the inclusion of such trivial cases in the firmware. I chose these examples on two grounds: First, they are all simple, almost primitive, but for this very reason, they demonstrate an idea without distraction. Second, having a piece of parallel python code is useful insofar as it tells us what to expect, and we can compare the results of the C implemetation to that of the native firmware." |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"# Setting up the environment, compiling the code\n", |
|||
"\n", |
|||
"\n", |
|||
"Beginning with the 1.10 version of micropython, it became quite simple to add a user-defined C module to the firmware. \n", |
|||
"\n", |
|||
"\n", |
|||
"-------------------------------\n", |
|||
"\n", |
|||
"```bash\n", |
|||
"\n", |
|||
"make USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all\n", |
|||
"```\n", |
|||
"or \n", |
|||
"\n", |
|||
"```bash\n", |
|||
"\n", |
|||
"make USER_C_MODULES=../../../modules all\n", |
|||
"```\n" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"The micropython codebase itself is set up a rather modular way. If you look at the top level directories," |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "code", |
|||
"execution_count": 5, |
|||
"metadata": { |
|||
"ExecuteTime": { |
|||
"end_time": "2019-07-07T05:29:49.203948Z", |
|||
"start_time": "2019-07-07T05:29:49.063375Z" |
|||
} |
|||
}, |
|||
"outputs": [ |
|||
{ |
|||
"name": "stdout", |
|||
"output_type": "stream", |
|||
"text": [ |
|||
"ACKNOWLEDGEMENTS docs extmod logo\t py\t tools\r\n", |
|||
"CODECONVENTIONS.md drivers lib mpy-cross README.md\r\n", |
|||
"CONTRIBUTING.md examples LICENSE ports\t tests\r\n" |
|||
] |
|||
} |
|||
], |
|||
"source": [ |
|||
"!ls ../../micropython/" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"at least two are of particular interest. Namely, `/py/`, where the python interpreter is implemented, and `/ports/`, which contains the hardware-specific files. " |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"# Developing your own module\n", |
|||
"\n", |
|||
"We begin with adding a simple module to the python interpreter. The module will have a single function that takes two numbers, and adds them. As a module, this is not particularly useful, but we can \n", |
|||
". However, I believe, starting with and understanding the simple is a really good way of learning. After getting to know the foundations, it will be rather trivial to extend the module with more useful functionality. \n", |
|||
"\n", |
|||
"\n", |
|||
"First I show the file in its entirety (20 something lines all in all), and then discuss the parts." |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": { |
|||
"ExecuteTime": { |
|||
"end_time": "2019-07-07T05:36:39.517066Z", |
|||
"start_time": "2019-07-07T05:36:33.293731Z" |
|||
} |
|||
}, |
|||
"source": [ |
|||
"```c\n", |
|||
"#include \"py/obj.h\"\n", |
|||
"#include \"py/runtime.h\"\n", |
|||
"#include \"py/builtin.h\"\n", |
|||
"\n", |
|||
"STATIC mp_obj_t simplefunction_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {\n", |
|||
" int a = mp_obj_get_int(a_obj);\n", |
|||
" int b = mp_obj_get_int(b_obj);\n", |
|||
" return mp_obj_new_int(a + b);\n", |
|||
"}\n", |
|||
"\n", |
|||
"STATIC MP_DEFINE_CONST_FUN_OBJ_2(simplefunction_add_ints_obj, simplefunction_add_ints);\n", |
|||
"\n", |
|||
"STATIC const mp_rom_map_elem_t simplefunction_module_globals_table[] = {\n", |
|||
" { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_simplefunction) },\n", |
|||
" { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&simplefunction_add_ints_obj) },\n", |
|||
"};\n", |
|||
"STATIC MP_DEFINE_CONST_DICT(simplefunction_module_globals, simplefunction_module_globals_table);\n", |
|||
"\n", |
|||
"const mp_obj_module_t simplefunction_user_cmodule = {\n", |
|||
" .base = { &mp_type_module },\n", |
|||
" .globals = (mp_obj_dict_t*)&simplefunction_module_globals,\n", |
|||
"};\n", |
|||
"\n", |
|||
"MP_REGISTER_MODULE(MP_QSTR_simplefunction, simplefunction_user_cmodule, MODULE_SIMPLEFUNCTION_ENABLED);\n", |
|||
"\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"You should be able to compile the module above by calling \n", |
|||
"\n", |
|||
"```bash\n", |
|||
"make USER_C_MODULES=../../../usermod/ all\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"### Defining user functions\n", |
|||
"\n", |
|||
"First, we define the function that is going to do the heavy lifting. By passing variables of `mp_obj_t` type, we make sure that the function will be able to accept values from the python console. If you happen to have an internal helper function in your module that is not exposed in python, you can pass whatever type you need. \n", |
|||
"Similarly, by returning an object of `mp_obj_t` type, we make the results visible to the interpreter, i.e., we can assign the value returned to variables. \n", |
|||
"\n", |
|||
"The downside of passing `mp_obj_t`s around is that you cannot simply assign them to usual C variables. This is why we have to invoke the `mp_obj_get_int()` function, and conversely, before returning the results, we have to do a type conversion to `mp_obj_t` by calling `mp_obj_new_int()`." |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"### Referring to user functions\n", |
|||
"\n", |
|||
"Once we implemented the functionality we need, we have to turn our function into a function object that the python interpreter can work with. This is what happens in the line\n", |
|||
"\n", |
|||
"```c\n", |
|||
"STATIC MP_DEFINE_CONST_FUN_OBJ_2(simplefunction_add_ints_obj, simplefunction_add_ints);\n", |
|||
"```\n", |
|||
"\n", |
|||
"The first argument is the name of the function object to which our actual function, the last argument, will be bound. Now, these `MP_DEFINE_CONST_FUN_OBJ_*` macros, defined in the header file `py/obj.h`, come in seven flavours, depending on what kind of, and how many arguments the function is supposed to take. In the example above, our function is meant to take two arguments, hence the 2 at the end of the macro name. Functions with 0 to 4 arguments can be bound in this way.\n", |
|||
"\n", |
|||
"But what, if you want a function with more than four arguments, as is the case many a time in python? In this case, one can make use of the \n", |
|||
"\n", |
|||
"```c\n", |
|||
"MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name)\n", |
|||
"```\n", |
|||
"\n", |
|||
"macro, where the second argument, an integer, gives the minimum number of arguments. The number of arguments can be bound from above by wrapping the function with \n", |
|||
"\n", |
|||
"```c\n", |
|||
"MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) \n", |
|||
"```\n", |
|||
"\n", |
|||
"Later we will see, how we can define functions that can also take keyword arguments." |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"At this point, we are more or less done with the C implementation of our function, but we still have to expose it. This we do by adding a table, an array of key/value pairs to the globals of our module, and bind the table to the `_module_globals` variable by applying the `MP_DEFINE_CONST_DICT` macro. This table should have at least one entry, the name of the module, which is going to be stored in the string `MP_QSTR___name__`. \n", |
|||
"\n", |
|||
"The other keys of the table are the pointers to the functions that we implemented, and the names that we want to call these functions in python itself. So, in the example below, our `simplefunction_add_ints` function will be invoked, when we call `add_ints` in the console.\n", |
|||
"\n", |
|||
"\n", |
|||
"```c\n", |
|||
"STATIC const mp_rom_map_elem_t simplefunction_module_globals_table[] = {\n", |
|||
" { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_simplefunction) },\n", |
|||
" { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&simplefunction_add_ints_obj) },\n", |
|||
"};\n", |
|||
"STATIC MP_DEFINE_CONST_DICT(simplefunction_module_globals, simplefunction_module_globals_table);\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"Having defined the function object, we have finally to register the function with \n", |
|||
"\n", |
|||
"```c\n", |
|||
"MP_REGISTER_MODULE(MP_QSTR_simplefunction, simplefunction_user_cmodule, MODULE_SIMPLEFUNCTION_ENABLED);\n", |
|||
"```\n", |
|||
"\n", |
|||
"This last line is particularly useful, because by setting the `MODULE_SIMPLEFUNCTION_ENABLED` variable in `mpconfigport.h`, you can selectively exclude modules from the compilation, i.e., if in `mpconfigport.h`, which should be in the root directory of the port you want to compile for, \n", |
|||
"\n", |
|||
"```c\n", |
|||
"#define MODULE_SIMPLEFUNCTION_ENABLED (1)\n", |
|||
"```\n", |
|||
"then `simplefunction` will be included in the firmware, while with\n", |
|||
"\n", |
|||
"```c\n", |
|||
"#define MODULE_SIMPLEFUNCTION_ENABLED (0)\n", |
|||
"```\n", |
|||
"\n", |
|||
"the module will be dropped, even though the source is in your modules folder." |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"## Function names\n", |
|||
"\n", |
|||
"Whenever you want your function to be visible from the interpreter, you have to add the function object to a dictionary. The dictionary \n", |
|||
"\n", |
|||
"```c\n", |
|||
"STATIC const mp_rom_map_elem_t test_locals_dict_table[] = {\n", |
|||
"\t{ MP_ROM_QSTR(MP_QSTR_somefunction), MP_ROM_PTR(&test_somefunction_obj) },\n", |
|||
"};\n", |
|||
"\n", |
|||
"\n", |
|||
"STATIC MP_DEFINE_CONST_DICT(test_locals_dict, test_locals_dict_table);\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"## Parsing arguments" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"## Parsing keyword arguments\n", |
|||
"\n", |
|||
"One of the most useful features of python is that functions can accept positional as well as keyword arguments, thereby providing a very flexible interface for feeding values to the function. In this section, we will see how we should set up our function, so that it can interpret what we passed in. \n", |
|||
"\n", |
|||
"It does not matter, whether we have positional or keyword arguments, at one point, the interpreter has to turn all arguments into a deterministic sequence of objects. We stipulate this sequence in the constant `mp_arg_t` type variable called `allowed_args[]`. This is an array of type `mp_arg_t`, which is nothing but two `uint16` values, and a union named `mp_arg_val_t`. This union holds the default value and the type of the variable that we want to pass. (This is all defined in `runtime.h`.) \n", |
|||
"\n", |
|||
"After parsing the arguments with `mp_arg_parse_all`, whatever was at the zeroth position of `allowed_args[]` will be called `args[0]`, the object at the first position of `allowed_args[]` will be turned into `args[1]`, and so on. \n", |
|||
"\n", |
|||
"```c\n", |
|||
"STATIC mp_obj_t kw_test(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {\n", |
|||
" static const mp_arg_t allowed_args[] = {\n", |
|||
"\t\t{ MP_QSTR_input, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} }, \n", |
|||
" { MP_QSTR_base, MP_ARG_INT, {.u_int = 12} },\n", |
|||
" { MP_QSTR_mode, MP_ARG_INT, {.u_int = 555} },\n", |
|||
" { MP_QSTR_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 33} },\n", |
|||
" { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 33} }, \n", |
|||
" };\n", |
|||
"\n", |
|||
" // parse args\n", |
|||
" mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];\n", |
|||
" mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);\n", |
|||
" if(MP_OBJ_IS_TYPE(args[0].u_rom_obj, &mp_type_tuple)) {\n", |
|||
"\t\tprintf(\"tuple!!!\");\n", |
|||
"\t}\n", |
|||
" printf(\"base: %lu\\n\\r\", args[1].u_int);\n", |
|||
" printf(\"mode: %lu\\n\\r\", args[2].u_int);\n", |
|||
" printf(\"address: %lu\\n\\r\", args[3].u_int); \n", |
|||
" return mp_const_none;\n", |
|||
"}\n", |
|||
"\n", |
|||
"STATIC MP_DEFINE_CONST_FUN_OBJ_KW(kw_test_obj, 1, kw_test);\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"# Working with classes\n", |
|||
"\n", |
|||
"Of course, python would not be python without classes. A module can also include the implementation of classes. The procedure is similar to what we have already seen in the context of standard functions, except that we have to define a structure that holds at least a string with the name of the class, a pointer to the initialisation and printout functions, and a local dictionary. A typical structure would look like\n", |
|||
"\n", |
|||
"```c\n", |
|||
"STATIC const mp_rom_map_elem_t test_locals_dict_table[] = {\n", |
|||
"\t{ MP_ROM_QSTR(MP_QSTR_method1), MP_ROM_PTR(&test_method1_obj) },\n", |
|||
"\t{ MP_ROM_QSTR(MP_QSTR_method2), MP_ROM_PTR(&test_method2_obj) },\n", |
|||
" ... \n", |
|||
"}\n", |
|||
"\n", |
|||
"const mp_obj_type_t simpleclass_type = {\n", |
|||
"\t{ &mp_type_type },\n", |
|||
"\t.name = MP_QSTR_simpleclass,\n", |
|||
"\t.print = simpleclass_print,\n", |
|||
"\t.make_new = simpleclass_make_new,\n", |
|||
"\t.locals_dict = (mp_obj_dict_t*)&simpleclass_locals_dict,\n", |
|||
"};\n", |
|||
"```\n", |
|||
"\n", |
|||
"The locals dictionary, `.locals_dict`, contains all user-facing methods and constants of the class, while the `myclass_type` structure's `name` member is what our class is going to be called. `.print` is roughly the equivalent of `__str__`, `.make_new` is the C name for `__init__`. " |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"In order to see how this all works, we are going to implement a very simple class, which holds two integer variables, and has a method that returns the sum of these two variables. In python, a possible realisation could look like this:" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "code", |
|||
"execution_count": 10, |
|||
"metadata": { |
|||
"ExecuteTime": { |
|||
"end_time": "2019-07-08T17:41:50.611648Z", |
|||
"start_time": "2019-07-08T17:41:50.594402Z" |
|||
} |
|||
}, |
|||
"outputs": [ |
|||
{ |
|||
"data": { |
|||
"text/plain": [ |
|||
"3" |
|||
] |
|||
}, |
|||
"execution_count": 10, |
|||
"metadata": {}, |
|||
"output_type": "execute_result" |
|||
} |
|||
], |
|||
"source": [ |
|||
"class myclass:\n", |
|||
" \n", |
|||
" def __init__(self, a, b):\n", |
|||
" self.a = a\n", |
|||
" self.b = b\n", |
|||
" \n", |
|||
" def mysum(self):\n", |
|||
" return self.a + self.b\n", |
|||
" \n", |
|||
" \n", |
|||
"A = myclass(1, 2)\n", |
|||
"A.mysum()" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"In addition to the class implementation above and to show how class methods and regular functions can live in the same module, we will also have a function, which is not bound to the class itself, and which adds the two components in the class, i.e., that is similar to " |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "code", |
|||
"execution_count": 11, |
|||
"metadata": { |
|||
"ExecuteTime": { |
|||
"end_time": "2019-07-08T17:45:01.645421Z", |
|||
"start_time": "2019-07-08T17:45:01.634882Z" |
|||
} |
|||
}, |
|||
"outputs": [ |
|||
{ |
|||
"data": { |
|||
"text/plain": [ |
|||
"3" |
|||
] |
|||
}, |
|||
"execution_count": 11, |
|||
"metadata": {}, |
|||
"output_type": "execute_result" |
|||
} |
|||
], |
|||
"source": [ |
|||
"def add(class_instance):\n", |
|||
" return class_instance.a + class_instance.b\n", |
|||
"\n", |
|||
"add(A)" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"(Note that retrieving values from the class in this way is not quite elegant. We would usually implement a getter method for that.)" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"```c\n", |
|||
"#include \"py/runtime.h\"\n", |
|||
"#include \"py/obj.h\"\n", |
|||
"\n", |
|||
"typedef struct _simpleclass_myclass_obj_t {\n", |
|||
"\tmp_obj_base_t base;\n", |
|||
"\tint16_t a;\n", |
|||
"\tint16_t b;\n", |
|||
"} simpleclass_myclass_obj_t;\n", |
|||
"\n", |
|||
"const mp_obj_type_t simpleclass_myclass_type;\n", |
|||
"\n", |
|||
"STATIC void myclass_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {\n", |
|||
"\t(void)kind;\n", |
|||
"\tsimpleclass_myclass_obj_t *self = MP_OBJ_TO_PTR(self_in);\n", |
|||
"\tmp_print_str(print, \"myclass(\");\n", |
|||
"\tprintf(\"%d, \", self->a);\n", |
|||
"\tprintf(\"%d)\", self->b);\n", |
|||
"}\n", |
|||
"\n", |
|||
"STATIC mp_obj_t myclass_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {\n", |
|||
"\tmp_arg_check_num(n_args, n_kw, 2, 2, true);\n", |
|||
"\tsimpleclass_myclass_obj_t *self = m_new_obj(simpleclass_myclass_obj_t);\n", |
|||
"\tself->base.type = &simpleclass_myclass_type;\n", |
|||
"\tself->a = mp_obj_get_int(args[0]);\n", |
|||
"\tself->b = mp_obj_get_int(args[1]);\n", |
|||
"\treturn MP_OBJ_FROM_PTR(self);\n", |
|||
"}\n", |
|||
"\n", |
|||
"// Class methods\n", |
|||
"STATIC mp_obj_t myclass_sum(mp_obj_t self_in) {\n", |
|||
"\tsimpleclass_myclass_obj_t *self = MP_OBJ_TO_PTR(self_in);\n", |
|||
"\treturn mp_obj_new_int(self->a + self->b);\n", |
|||
"}\n", |
|||
"\n", |
|||
"MP_DEFINE_CONST_FUN_OBJ_1(myclass_sum_obj, myclass_sum);\n", |
|||
"\n", |
|||
"\n", |
|||
"STATIC const mp_rom_map_elem_t myclass_locals_dict_table[] = {\n", |
|||
"\t{ MP_ROM_QSTR(MP_QSTR_mysum), MP_ROM_PTR(&myclass_sum_obj) },\n", |
|||
"};\n", |
|||
"\n", |
|||
"STATIC MP_DEFINE_CONST_DICT(myclass_locals_dict, myclass_locals_dict_table);\n", |
|||
"\n", |
|||
"const mp_obj_type_t simpleclass_myclass_type = {\n", |
|||
"\t{ &mp_type_type },\n", |
|||
"\t.name = MP_QSTR_simpleclass,\n", |
|||
"\t.print = myclass_print,\n", |
|||
"\t.make_new = myclass_make_new,\n", |
|||
"\t.locals_dict = (mp_obj_dict_t*)&myclass_locals_dict,\n", |
|||
"};\n", |
|||
"\n", |
|||
"// Module functions\n", |
|||
"STATIC mp_obj_t simpleclass_add(const mp_obj_t o_in) {\n", |
|||
"\tsimpleclass_myclass_obj_t *class_instance = MP_OBJ_TO_PTR(o_in);\n", |
|||
"\treturn mp_obj_new_int(class_instance->a + class_instance->b);\n", |
|||
"}\n", |
|||
"\n", |
|||
"MP_DEFINE_CONST_FUN_OBJ_1(simpleclass_add_obj, simpleclass_add);\n", |
|||
"\n", |
|||
"STATIC const mp_map_elem_t simpleclass_globals_table[] = {\n", |
|||
"\t{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_simpleclass) },\n", |
|||
"\t{ MP_OBJ_NEW_QSTR(MP_QSTR_myclass), (mp_obj_t)&simpleclass_myclass_type },\t\n", |
|||
"\t{ MP_OBJ_NEW_QSTR(MP_QSTR_add), (mp_obj_t)&simpleclass_add_obj },\n", |
|||
"};\n", |
|||
"\n", |
|||
"STATIC MP_DEFINE_CONST_DICT (\n", |
|||
"\tmp_module_simpleclass_globals,\n", |
|||
"\tsimpleclass_globals_table\n", |
|||
");\n", |
|||
"\n", |
|||
"const mp_obj_module_t simpleclass_user_cmodule = {\n", |
|||
"\t.base = { &mp_type_module },\n", |
|||
"\t.globals = (mp_obj_dict_t*)&mp_module_simpleclass_globals,\n", |
|||
"};\n", |
|||
"\n", |
|||
"MP_REGISTER_MODULE(MP_QSTR_simpleclass, simpleclass_user_cmodule, MODULE_SIMPLECLASS_ENABLED);\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"## Special methods\n", |
|||
"\n", |
|||
"Python has a number of special methods, which will make a class behave as a native object. So, e.g., if a class implements the `__add__(self, other)` method, then instances of the class can be added with the `+` operator. Here is an example:" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "code", |
|||
"execution_count": 4, |
|||
"metadata": { |
|||
"ExecuteTime": { |
|||
"end_time": "2019-07-01T05:09:35.616129Z", |
|||
"start_time": "2019-07-01T05:09:35.602178Z" |
|||
} |
|||
}, |
|||
"outputs": [ |
|||
{ |
|||
"data": { |
|||
"text/plain": [ |
|||
"3" |
|||
] |
|||
}, |
|||
"execution_count": 4, |
|||
"metadata": {}, |
|||
"output_type": "execute_result" |
|||
} |
|||
], |
|||
"source": [ |
|||
"class Adder:\n", |
|||
" \n", |
|||
" def __init__(self, value):\n", |
|||
" self.value = value\n", |
|||
" \n", |
|||
" def __add__(self, other):\n", |
|||
" self.value = self.value + other.value\n", |
|||
" return self\n", |
|||
"\n", |
|||
"a = Adder(1)\n", |
|||
"b = Adder(2)\n", |
|||
"\n", |
|||
"c = a + b\n", |
|||
"c.value" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"Note that, while the above example is not particularly useful, it proves the point: upon calling the `+` operator, the values of `a`, and `b` are added. If we had left out the implementation of the `__add__` method, the python interpreter would not have a clue as to what to do with the objects. You can see for yourself what happens, if one is sloppy:" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "code", |
|||
"execution_count": 5, |
|||
"metadata": { |
|||
"ExecuteTime": { |
|||
"end_time": "2019-07-01T05:13:52.179383Z", |
|||
"start_time": "2019-07-01T05:13:50.426879Z" |
|||
} |
|||
}, |
|||
"outputs": [ |
|||
{ |
|||
"ename": "TypeError", |
|||
"evalue": "unsupported operand type(s) for +: 'Adder' and 'Adder'", |
|||
"output_type": "error", |
|||
"traceback": [ |
|||
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", |
|||
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", |
|||
"\u001b[0;32m<ipython-input-5-635006a6f7bc>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mAdder\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 10\u001b[0m \u001b[0mc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", |
|||
"\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'Adder' and 'Adder'" |
|||
] |
|||
} |
|||
], |
|||
"source": [ |
|||
"class Adder:\n", |
|||
" \n", |
|||
" def __init__(self, value):\n", |
|||
" self.value = value\n", |
|||
"\n", |
|||
"a = Adder(1)\n", |
|||
"b = Adder(2)\n", |
|||
"\n", |
|||
"c = a + b\n", |
|||
"c.value" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"Indeed, we do not support the `+` operator.\n", |
|||
"\n", |
|||
"Now, the problem is that in the C implementation, these special methods have to be treated in a special way. The naive approach would be to add the pointer to the function to the locals dictionary as \n", |
|||
"\n", |
|||
"```c\n", |
|||
"STATIC const mp_rom_map_elem_t test_locals_dict_table[] = {\n", |
|||
"\t{ MP_ROM_QSTR(MP_QSTR___add__), MP_ROM_PTR(&test_add_obj) },\n", |
|||
"};\n", |
|||
"```\n", |
|||
"but that would not work. Well, this is not entirely true: the `+` operator would not work, but one could still call the method explicitly as\n", |
|||
"\n", |
|||
"```python\n", |
|||
"a = Adder(1)\n", |
|||
"b = Adder(2)\n", |
|||
"\n", |
|||
"a.__add__(b)\n", |
|||
"\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"Before we can actually add the `+` operator to the class, we should note that there are two kinds of special methods (TODO: THERE ARE MORE, THIS HAS TO BE EXPLAINED A BIT), namely the unary and the binary operators. \n", |
|||
"\n", |
|||
"In the first group are those, whose sole argument is the class instance itself. Two frequently used cases are the length operator, `len`, and `bool`. So, e.g., if your class implements the `__len__(self)` method, and the method returns an integer, then you can call the `len` function in the console\n", |
|||
"\n", |
|||
"```python\n", |
|||
"len(myclass)\n", |
|||
"```\n", |
|||
"\n", |
|||
"In the second category of operators are those, which require a left, as well as a right hand side. An example for this was the `__add__` method in our `Adder` class.\n", |
|||
"\n" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"## Defining constants\n", |
|||
"\n", |
|||
"Constants can be added to the locals dictionary as any other object. So, e.g., if we wanted to define the constant MAGIC, we could do that as follows\n", |
|||
"\n", |
|||
"```c\n", |
|||
"\n", |
|||
"#define MAGIC 42\n", |
|||
"\n", |
|||
"STATIC const mp_rom_map_elem_t test_locals_dict_table[] = {\n", |
|||
"\t{ MP_ROM_QSTR(MP_QSTR_MAGIC), MP_ROM_INT(MAGIC) },\n", |
|||
"};\n", |
|||
"```\n", |
|||
"The constant would then be accessible in the interpreter as\n", |
|||
"\n", |
|||
"```python\n", |
|||
"\n", |
|||
"import test\n", |
|||
"\n", |
|||
"test.MAGIC\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"## The complete code\n", |
|||
"\n", |
|||
"Having seen how components of a class should be implemented, we can bring all parts together. We will define a class that has two methods, one with positional arguments, and one with keyword arguments, has a class-specific constant, and can deal with the `+` and `*` operators. You should be able to drop this code into your user-defined modules directory, and compile it without difficulties.\n", |
|||
"\n", |
|||
"```c\n", |
|||
"\n", |
|||
"```" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"# Dealing with iterables\n", |
|||
"\n", |
|||
"\n", |
|||
"In python, an iterable is basically an object that you can have in a `for` loop:\n", |
|||
"\n", |
|||
"```python\n", |
|||
"for item in my_iterable:\n", |
|||
" print(item)\n", |
|||
"```\n", |
|||
"\n", |
|||
"Amongs others, lists, tuples, and ranges are iterables. The key is that these object have a special internal method, and iterator, attached to them. This iterator method is responsible for keeping track of the index during the iteration, and serving the objects in the iterable one by one to the `for` loop. When writing our own iterable, we will look under the hood, and see how this all works at the C level. For now, we are going to discuss only, how we can *consume* the content of an iterable in the C code. \n", |
|||
"\n", |
|||
"\n", |
|||
"In order to demonstrate the use of an iterator, we are going to write a function that takes the values from a list, and squares them. \n", |
|||
"\n", |
|||
"\n", |
|||
"\n", |
|||
"\n", |
|||
"\n", |
|||
"If you want to find out, whether a particular variable is an iterable, you can \n", |
|||
"\n", |
|||
"\n", |
|||
"`objlist.c`, `objtuple.c`, `objarray.c`, `objrange.c` etc." |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "markdown", |
|||
"metadata": {}, |
|||
"source": [ |
|||
"# Profiling\n", |
|||
"\n", |
|||
"There are times, when you might want to find out how long a particular operation takes. If you are interested in the execution time of a complete function, you can measure it simply by making use of the python interpreter\n", |
|||
"\n", |
|||
"```python\n", |
|||
"\n", |
|||
"from utime import ticks_us, diff_ticks\n", |
|||
"\n", |
|||
"now = ticks_us\n", |
|||
"run_my_function()\n", |
|||
"then = diff_ticks(ticks_us(), now)\n", |
|||
"\n", |
|||
"print(\"function run_my_function() took %d us to run\"%then)\n", |
|||
"\n", |
|||
"```\n", |
|||
"\n", |
|||
"In fact, since our function is flanked by two other statements, this construct easily lends itself to a decorator implementation. \n", |
|||
"\n", |
|||
"(If you need an even better estimate, you can yank run_my_function(): in this way, you would get the costs of measuring time itself:\n", |
|||
"\n", |
|||
"```python\n", |
|||
"\n", |
|||
"from utime import ticks_us, diff_ticks\n", |
|||
"\n", |
|||
"now = ticks_us\n", |
|||
"then = diff_ticks(ticks_us(), now)\n", |
|||
"\n", |
|||
"print(\"the time measurement took %d us\"%then)\n", |
|||
"\n", |
|||
"```\n", |
|||
"Then you subtract the results of the second measurement from those of the first.)" |
|||
] |
|||
}, |
|||
{ |
|||
"cell_type": "code", |
|||
"execution_count": null, |
|||
"metadata": {}, |
|||
"outputs": [], |
|||
"source": [] |
|||
} |
|||
], |
|||
"metadata": { |
|||
"kernelspec": { |
|||
"display_name": "Python 3", |
|||
"language": "python", |
|||
"name": "python3" |
|||
}, |
|||
"language_info": { |
|||
"codemirror_mode": { |
|||
"name": "ipython", |
|||
"version": 3 |
|||
}, |
|||
"file_extension": ".py", |
|||
"mimetype": "text/x-python", |
|||
"name": "python", |
|||
"nbconvert_exporter": "python", |
|||
"pygments_lexer": "ipython3", |
|||
"version": "3.6.8" |
|||
}, |
|||
"toc": { |
|||
"base_numbering": 1, |
|||
"nav_menu": {}, |
|||
"number_sections": true, |
|||
"sideBar": true, |
|||
"skip_h1_title": false, |
|||
"title_cell": "Table of Contents", |
|||
"title_sidebar": "Contents", |
|||
"toc_cell": false, |
|||
"toc_position": {}, |
|||
"toc_section_display": true, |
|||
"toc_window_display": true |
|||
} |
|||
}, |
|||
"nbformat": 4, |
|||
"nbformat_minor": 2 |
|||
} |
Loading…
Reference in new issue