Skip to content

Commit beb4c31

Browse files
committed
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 <[email protected]>
1 parent e3ef682 commit beb4c31

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

py/runtime.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,14 +1562,11 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) {
15621562
mp_obj_t dest[2];
15631563

15641564
mp_load_method_maybe(module, name, dest);
1565-
15661565
if (dest[1] != MP_OBJ_NULL) {
1567-
// Hopefully we can't import bound method from an object
1568-
import_error:
1569-
mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name);
1570-
}
1571-
1572-
if (dest[0] != MP_OBJ_NULL) {
1566+
// Importing a bound method from a class instance.
1567+
return mp_obj_new_bound_meth(dest[0], dest[1]);
1568+
} else if (dest[0] != MP_OBJ_NULL) {
1569+
// Importing a function or attribute.
15731570
return dest[0];
15741571
}
15751572

@@ -1602,6 +1599,9 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) {
16021599
goto import_error;
16031600

16041601
#endif
1602+
1603+
import_error:
1604+
mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("can't import name %q"), name);
16051605
}
16061606

16071607
void mp_import_all(mp_obj_t module) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Test importing a method from a class instance.
2+
# This is not a common thing to do, but ensures MicroPython has the same semantics as CPython.
3+
4+
import sys
5+
6+
if not hasattr(sys, "modules"):
7+
print("SKIP")
8+
raise SystemExit
9+
10+
11+
class A:
12+
def __init__(self, value):
13+
self.value = value
14+
15+
def meth(self):
16+
return self.value
17+
18+
def meth_with_arg(self, a):
19+
return [self.value, a]
20+
21+
22+
# Register a class instance as the module "mod".
23+
sys.modules["mod"] = A(1)
24+
25+
# Try importing it as a module.
26+
import mod
27+
28+
print(mod.meth())
29+
print(mod.meth_with_arg(2))
30+
31+
# Change the module.
32+
sys.modules["mod"] = A(3)
33+
34+
# Try importing it using "from ... import".
35+
from mod import meth, meth_with_arg
36+
37+
print(meth())
38+
print(meth_with_arg(4))

0 commit comments

Comments
 (0)