From beb4c3188f5c26ede14134db1d34f817793b7e46 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 18 Sep 2025 23:55:36 +1000 Subject: [PATCH] py/runtime: Support importing a method from an instance. This change follows CPython behaviour, allowing use of: from instance import method to import a bound method from a class instance, eg registered via setting `sys.modules["instance"] = instance`. Admittedly this is probably a very rarely used pattern in Python, but it resolves a long standing comment about whether or not this is actually possible (it turns out it is possible!). A test is added to show how it works. The main reason for this change is to fix a problem with imports in the webassembly port: prior to this fix, it was not possible to do `from js_module import function`, where `js_module` is a JavaScript object registered to be visible to Python through the webassembly API function `registerJsModule(js_module)`. But now with this fix that is possible. Signed-off-by: Damien George --- py/runtime.c | 14 +++++----- tests/basics/import_instance_method.py | 38 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 tests/basics/import_instance_method.py diff --git a/py/runtime.c b/py/runtime.c index 0ab0626ef9..61aeb83f8d 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1562,14 +1562,11 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) { mp_obj_t dest[2]; mp_load_method_maybe(module, name, dest); - if (dest[1] != MP_OBJ_NULL) { - // Hopefully we can't import bound method from an object - import_error: - mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name); - } - - if (dest[0] != MP_OBJ_NULL) { + // Importing a bound method from a class instance. + return mp_obj_new_bound_meth(dest[0], dest[1]); + } else if (dest[0] != MP_OBJ_NULL) { + // Importing a function or attribute. return dest[0]; } @@ -1602,6 +1599,9 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) { goto import_error; #endif + +import_error: + mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name); } void mp_import_all(mp_obj_t module) { diff --git a/tests/basics/import_instance_method.py b/tests/basics/import_instance_method.py new file mode 100644 index 0000000000..d25b70ac5f --- /dev/null +++ b/tests/basics/import_instance_method.py @@ -0,0 +1,38 @@ +# Test importing a method from a class instance. +# This is not a common thing to do, but ensures MicroPython has the same semantics as CPython. + +import sys + +if not hasattr(sys, "modules"): + print("SKIP") + raise SystemExit + + +class A: + def __init__(self, value): + self.value = value + + def meth(self): + return self.value + + def meth_with_arg(self, a): + return [self.value, a] + + +# Register a class instance as the module "mod". +sys.modules["mod"] = A(1) + +# Try importing it as a module. +import mod + +print(mod.meth()) +print(mod.meth_with_arg(2)) + +# Change the module. +sys.modules["mod"] = A(3) + +# Try importing it using "from ... import". +from mod import meth, meth_with_arg + +print(meth()) +print(meth_with_arg(4))