summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurens Valk <laurens@pybricks.com>2022-12-05 16:51:20 +0100
committerDamien George <damien@micropython.org>2024-07-25 11:57:13 +1000
commit19b1333cb1376ef60376a07e8e76a41854014840 (patch)
tree71527e14bca10b0be10e2e52c655baa35f39dfe5
parent7fe8f030eea0015962e729eae1f1c309dc83a469 (diff)
examples/usercmodule/cexample: Add more advanced native class.
This adds a separate `AdvancedTimer` class that demonstrates a few more advanced concepts usch as custom handlers for printing and attributes. Signed-off-by: Laurens Valk <laurens@pybricks.com>
-rw-r--r--examples/usercmodule/cexample/examplemodule.c82
-rw-r--r--tests/misc/cexample_class.py17
-rw-r--r--tests/misc/cexample_class.py.exp6
-rw-r--r--tests/misc/cexample_module.py1
4 files changed, 106 insertions, 0 deletions
diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c
index 2988fbd56..d13515e72 100644
--- a/examples/usercmodule/cexample/examplemodule.c
+++ b/examples/usercmodule/cexample/examplemodule.c
@@ -68,6 +68,87 @@ MP_DEFINE_CONST_OBJ_TYPE(
locals_dict, &example_Timer_locals_dict
);
+// What follows is a *separate* class definition that demonstrates more
+// advanced techniques to implement other Python-like features, such as:
+//
+// - A custom representation for __repr__ and __str__.
+// - Custom attribute handling to create a read/write "property".
+//
+// It re-uses some of the elements of the basic Timer class. This is allowed
+// because they both use example_Timer_obj_t as the instance structure.
+
+// Handles AdvancedTimer.__repr__, AdvancedTimer.__str__.
+static void example_AdvancedTimer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+
+ // Get the elapsed time. In this case, it's also a demonstration of calling
+ // the equivalent of self.time() in the C API. This is not usually very
+ // efficient, but it can sometimes be useful.
+ mp_uint_t elapsed = mp_obj_get_int(example_Timer_time(self_in));
+
+ // We'll make all representations print at least the class name.
+ mp_printf(print, "%q()", MP_QSTR_AdvancedTimer);
+
+ // Decide what else to print based on print kind.
+ if (kind == PRINT_STR) {
+ // For __str__, let's attempt to make it more readable.
+ mp_printf(print, " # created %d seconds ago", elapsed / 1000);
+ }
+}
+
+// Handles AdvancedTimer.seconds for reading and writing.
+static void example_AdvancedTimer_attribute_handler(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+
+ // In this example, we only want to handle the .seconds attribute in a
+ // special way.
+ if (attr != MP_QSTR_seconds) {
+ // Attribute not found, continue lookup in locals dict. This way,
+ // methods like .time() will be handled normally.
+ dest[1] = MP_OBJ_SENTINEL;
+ return;
+ }
+
+ // Get reference to AdvancedTimer instance.
+ example_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ // Check if this is a read operation.
+ if (dest[0] == MP_OBJ_NULL) {
+ // It's read, so "return" elapsed seconds by storing it in dest[0].
+ mp_uint_t elapsed = mp_hal_ticks_ms() - self->start_time;
+ dest[0] = mp_obj_new_int_from_uint(elapsed / 1000);
+ return;
+ }
+ // Check if this is a delete or store operation.
+ else if (dest[0] == MP_OBJ_SENTINEL) {
+ // It's delete or store. Now check which one.
+ if (dest[1] == MP_OBJ_NULL) {
+ // It's delete. But in this example we don't want to allow it
+ // so we just return.
+ return;
+ } else {
+ // It's write. First, get the value that the user is trying to set.
+ mp_uint_t desired_ms = mp_obj_get_int(dest[1]) * 1000;
+ // Use it to update the start time. This way, the next read will
+ // report the updated time.
+ self->start_time = mp_hal_ticks_ms() - desired_ms;
+
+ // Indicate successful store.
+ dest[0] = MP_OBJ_NULL;
+ return;
+ }
+ }
+}
+
+// This defines the type(AdvancedTimer) object.
+MP_DEFINE_CONST_OBJ_TYPE(
+ example_type_AdvancedTimer,
+ MP_QSTR_AdvancedTimer,
+ MP_TYPE_FLAG_NONE,
+ attr, example_AdvancedTimer_attribute_handler,
+ print, example_AdvancedTimer_print,
+ make_new, example_Timer_make_new,
+ locals_dict, &example_Timer_locals_dict
+ );
+
// Define all attributes of the module.
// Table entries are key/value pairs of the attribute name (a string)
// and the MicroPython object reference.
@@ -77,6 +158,7 @@ static const mp_rom_map_elem_t example_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) },
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
{ MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&example_type_Timer) },
+ { MP_ROM_QSTR(MP_QSTR_AdvancedTimer), MP_ROM_PTR(&example_type_AdvancedTimer) },
};
static MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
diff --git a/tests/misc/cexample_class.py b/tests/misc/cexample_class.py
index 6b8718ad8..06d741922 100644
--- a/tests/misc/cexample_class.py
+++ b/tests/misc/cexample_class.py
@@ -22,3 +22,20 @@ t_end = timer.time()
print(timer)
print(0 <= t_start <= TOLERANCE_MS)
print(SLEEP_MS - TOLERANCE_MS <= t_end <= SLEEP_MS + TOLERANCE_MS)
+
+advanced_timer = cexample.AdvancedTimer()
+
+time.sleep_ms(100)
+
+print(repr(advanced_timer))
+print(str(advanced_timer))
+
+print(advanced_timer.seconds)
+advanced_timer.seconds = 123
+print(advanced_timer.seconds)
+print(advanced_timer.time() < 123000 + TOLERANCE_MS)
+
+try:
+ advanced_timer.seconds = "bad input"
+except TypeError:
+ print("TypeError")
diff --git a/tests/misc/cexample_class.py.exp b/tests/misc/cexample_class.py.exp
index b9a06602a..a86d4d14f 100644
--- a/tests/misc/cexample_class.py.exp
+++ b/tests/misc/cexample_class.py.exp
@@ -1,3 +1,9 @@
<Timer>
True
True
+AdvancedTimer()
+AdvancedTimer() # created 0 seconds ago
+0
+123
+True
+TypeError
diff --git a/tests/misc/cexample_module.py b/tests/misc/cexample_module.py
index c1da2ecf7..979c1fa24 100644
--- a/tests/misc/cexample_module.py
+++ b/tests/misc/cexample_module.py
@@ -12,5 +12,6 @@ print(cexample.__name__)
d = dir(cexample)
d.index("add_ints")
d.index("Timer")
+d.index("AdvancedTimer")
print(cexample.add_ints(1, 3))