Browse Source

py/builtinimport: Optimise sub-package loading.

This makes it so that sub-packages are resolved relative to their parent's
`__path__`, rather than re-resolving each parent's filesystem path.

The previous behavior was that `import foo.bar` would first re-search
`sys.path` for `foo`, then use the resulting path to find `bar`.

For already-loaded and u-prefixed modules, because we no longer need to
build the path from level to level, we no longer unnecessarily search
the filesystem. This should improve startup time.

Explicitly makes the resolving process clear:
 - Loaded modules are returned immediately without touching the filesystem.
 - Exact-match of builtins are also returned immediately.
 - Then the filesystem search happens.
 - If that fails, then the weak-link handling is applied.

This maintains the existing behavior: if a user writes `import time` they
will get time.py if it exits, otherwise the built-in utime. Whereas `import
utime` will always return the built-in.

This also fixes a regression from a7fa18c203
where we search the filesystem for built-ins. It is now only possible to
override u-prefixed builtins. This will remove a lot of filesystem stats
at startup, as micropython-specific modules (e.g. `pyb`) will no longer
attempt to look at the filesystem.

Added several improvements to the comments and some minor renaming and
refactoring to make it clearer how the import mechanism works. Overall
code size diff is +56 bytes on STM32.

This work was funded through GitHub Sponsors.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
pull/11456/head
Jim Mussared 2 years ago
committed by Damien George
parent
commit
525557738c
  1. 4
      ports/unix/main.c
  2. 307
      py/builtinimport.c
  3. 2
      py/obj.h
  4. 32
      py/objmodule.c
  5. 3
      py/objmodule.h
  6. 3
      py/runtime.c

4
ports/unix/main.c

@ -664,7 +664,9 @@ MP_NOINLINE int main_(int argc, char **argv) {
return handle_uncaught_exception(nlr.ret_val) & 0xff;
}
if (mp_obj_is_package(mod) && !subpkg_tried) {
mp_obj_t dest[2];
mp_load_method_maybe(mod, MP_QSTR___path__, dest);
if (dest[0] != MP_OBJ_NULL && !subpkg_tried) {
subpkg_tried = true;
vstr_t vstr;
int len = strlen(argv[a + 1]);

307
py/builtinimport.c

@ -62,39 +62,39 @@ STATIC qstr make_weak_link_name(vstr_t *buffer, qstr name) {
// Virtual sys.path entry that maps to the frozen modules.
#define MP_FROZEN_PATH_PREFIX ".frozen/"
bool mp_obj_is_package(mp_obj_t module) {
mp_obj_t dest[2];
mp_load_method_maybe(module, MP_QSTR___path__, dest);
return dest[0] != MP_OBJ_NULL;
}
// Wrapper for mp_import_stat (which is provided by the port, and typically
// uses mp_vfs_import_stat) to also search frozen modules. Given an exact
// path to a file or directory (e.g. "foo/bar", foo/bar.py" or "foo/bar.mpy"),
// will return whether the path is a file, directory, or doesn't exist.
STATIC mp_import_stat_t stat_path_or_frozen(const char *path) {
STATIC mp_import_stat_t stat_path(const char *path) {
#if MICROPY_MODULE_FROZEN
// Only try and load as a frozen module if it starts with .frozen/.
const int frozen_path_prefix_len = strlen(MP_FROZEN_PATH_PREFIX);
if (strncmp(path, MP_FROZEN_PATH_PREFIX, frozen_path_prefix_len) == 0) {
// Just stat (which is the return value), don't get the data.
return mp_find_frozen_module(path + frozen_path_prefix_len, NULL, NULL);
}
#endif
return mp_import_stat(path);
}
// Given a path to a .py file, try and find this path as either a .py or .mpy
// in either the filesystem or frozen modules.
// Stat a given filesystem path to a .py file. If the file does not exist,
// then attempt to stat the corresponding .mpy file, and update the path
// argument. This is the logic that makes .py files take precedent over .mpy
// files. This uses stat_path above, rather than mp_import_stat directly, so
// that the .frozen path prefix is handled.
STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) {
mp_import_stat_t stat = stat_path_or_frozen(vstr_null_terminated_str(path));
mp_import_stat_t stat = stat_path(vstr_null_terminated_str(path));
if (stat == MP_IMPORT_STAT_FILE) {
return stat;
}
#if MICROPY_PERSISTENT_CODE_LOAD
// Didn't find .py -- try the .mpy instead by inserting an 'm' into the '.py'.
// Note: There's no point doing this if it's a frozen path, but adding the check
// would be extra code, and no harm letting mp_find_frozen_module fail instead.
vstr_ins_byte(path, path->len - 2, 'm');
stat = stat_path_or_frozen(vstr_null_terminated_str(path));
stat = stat_path(vstr_null_terminated_str(path));
if (stat == MP_IMPORT_STAT_FILE) {
return stat;
}
@ -104,15 +104,17 @@ STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) {
}
// Given an import path (e.g. "foo/bar"), try and find "foo/bar" (a directory)
// or "foo/bar.(m)py" in either the filesystem or frozen modules.
STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) {
mp_import_stat_t stat = stat_path_or_frozen(vstr_null_terminated_str(path));
// or "foo/bar.(m)py" in either the filesystem or frozen modules. If the
// result is a file, the path argument will be updated to include the file
// extension.
STATIC mp_import_stat_t stat_module(vstr_t *path) {
mp_import_stat_t stat = stat_path(vstr_null_terminated_str(path));
DEBUG_printf("stat %s: %d\n", vstr_str(path), stat);
if (stat == MP_IMPORT_STAT_DIR) {
return stat;
}
// not a directory, add .py and try as a file
// Not a directory, add .py and try as a file.
vstr_add_str(path, ".py");
return stat_file_py_or_mpy(path);
}
@ -120,8 +122,8 @@ STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) {
// Given a top-level module name, try and find it in each of the sys.path
// entries. Note: On success, the dest argument will be updated to the matching
// path (i.e. "<entry>/mod_name(.py)").
STATIC mp_import_stat_t stat_top_level_dir_or_file(qstr mod_name, vstr_t *dest) {
DEBUG_printf("stat_top_level_dir_or_file: '%s'\n", qstr_str(mod_name));
STATIC mp_import_stat_t stat_top_level(qstr mod_name, vstr_t *dest) {
DEBUG_printf("stat_top_level: '%s'\n", qstr_str(mod_name));
#if MICROPY_PY_SYS
size_t path_num;
mp_obj_t *path_items;
@ -138,7 +140,7 @@ STATIC mp_import_stat_t stat_top_level_dir_or_file(qstr mod_name, vstr_t *dest)
vstr_add_char(dest, PATH_SEP_CHAR[0]);
}
vstr_add_str(dest, qstr_str(mod_name));
mp_import_stat_t stat = stat_dir_or_file(dest);
mp_import_stat_t stat = stat_module(dest);
if (stat != MP_IMPORT_STAT_NO_EXIST) {
return stat;
}
@ -152,7 +154,7 @@ STATIC mp_import_stat_t stat_top_level_dir_or_file(qstr mod_name, vstr_t *dest)
// mp_sys_path is not enabled, so just stat the given path directly.
vstr_add_str(dest, qstr_str(mod_name));
return stat_dir_or_file(dest);
return stat_module(dest);
#endif
}
@ -350,128 +352,168 @@ STATIC void evaluate_relative_import(mp_int_t level, const char **module_name, s
}
// Load a module at the specified absolute path, possibly as a submodule of the given outer module.
// full_mod_name: The full absolute path to this module (e.g. "foo.bar.baz").
// full_mod_name: The full absolute path up to this level (e.g. "foo.bar.baz").
// level_mod_name: The final component of the path (e.g. "baz").
// outer_module_obj: The parent module (we need to store this module as an
// attribute on it) (or MP_OBJ_NULL for top-level).
// path: The filesystem path where we found the parent module
// (or empty for a top level module).
// override_main: Whether to set the __name__ to "__main__" (and use __main__
// for the actual path).
STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, mp_obj_t outer_module_obj, vstr_t *path, bool override_main) {
mp_import_stat_t stat = MP_IMPORT_STAT_NO_EXIST;
STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, mp_obj_t outer_module_obj, bool override_main) {
// Immediately return if the module at this level is already loaded.
mp_map_elem_t *elem;
// Exact-match of built-in (or already-loaded) takes priority.
mp_obj_t module_obj = mp_module_get_loaded_or_builtin(full_mod_name);
#if MICROPY_PY_SYS
// If sys.path is empty, the intention is to force using a built-in. This
// means we should also ignore any loaded modules with the same name
// which may have come from the filesystem.
size_t path_num;
mp_obj_t *path_items;
mp_obj_list_get(mp_sys_path, &path_num, &path_items);
if (path_num)
#endif
{
elem = mp_map_lookup(&MP_STATE_VM(mp_loaded_modules_dict).map, MP_OBJ_NEW_QSTR(full_mod_name), MP_MAP_LOOKUP);
if (elem) {
return elem->value;
}
}
// Even if we find the module, go through the motions of searching for it
// because we may actually be in the process of importing a sub-module.
// So we need to (re-)find the correct path to be finding the sub-module
// on the next iteration of process_import_at_level.
VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX);
mp_import_stat_t stat = MP_IMPORT_STAT_NO_EXIST;
mp_obj_t module_obj;
if (outer_module_obj == MP_OBJ_NULL) {
DEBUG_printf("Searching for top-level module\n");
// An exact match of a built-in will always bypass the filesystem.
// Note that CPython-compatible built-ins are named e.g. utime, so this
// means that an exact match is only for `import utime`, so `import
// time` will search the filesystem and failing that hit the weak
// link handling below. Whereas micropython-specific built-ins like
// `micropython`, `pyb`, `network`, etc will match exactly and cannot
// be overridden by the filesystem.
module_obj = mp_module_get_builtin(level_mod_name);
if (module_obj != MP_OBJ_NULL) {
return module_obj;
}
#if MICROPY_PY_SYS
// Never allow sys to be overridden from the filesystem. If weak links
// are disabled, then this also provides a default weak link so that
// `import sys` is treated like `import usys` (and therefore bypasses
// the filesystem).
if (level_mod_name == MP_QSTR_sys) {
return MP_OBJ_FROM_PTR(&mp_module_sys);
}
#endif
// First module in the dotted-name; search for a directory or file
// relative to all the locations in sys.path.
stat = stat_top_level_dir_or_file(full_mod_name, path);
stat = stat_top_level(level_mod_name, &path);
// If the module "foo" doesn't exist on the filesystem, and it's not a
// builtin, try and find "ufoo" as a built-in. (This feature was
// formerly known as "weak links").
#if MICROPY_MODULE_WEAK_LINKS
if (stat == MP_IMPORT_STAT_NO_EXIST && module_obj == MP_OBJ_NULL) {
qstr umodule_name = make_weak_link_name(path, level_mod_name);
if (stat == MP_IMPORT_STAT_NO_EXIST) {
// No match on the filesystem. (And not a built-in either).
// If "foo" was requested, then try "ufoo" as a built-in. This
// allows `import time` to use built-in `utime`, unless `time`
// exists on the filesystem. This feature was formerly known
// as "weak links".
qstr umodule_name = make_weak_link_name(&path, level_mod_name);
module_obj = mp_module_get_builtin(umodule_name);
}
#elif MICROPY_PY_SYS
if (stat == MP_IMPORT_STAT_NO_EXIST && module_obj == MP_OBJ_NULL && level_mod_name == MP_QSTR_sys) {
module_obj = MP_OBJ_FROM_PTR(&mp_module_sys);
if (module_obj != MP_OBJ_NULL) {
return module_obj;
}
}
#endif
} else {
DEBUG_printf("Searching for sub-module\n");
// Add the current part of the module name to the path.
vstr_add_char(path, PATH_SEP_CHAR[0]);
vstr_add_str(path, qstr_str(level_mod_name));
// If the outer module is a package, it will have __path__ set.
// We can use that as the path to search inside.
mp_obj_t dest[2];
mp_load_method_maybe(outer_module_obj, MP_QSTR___path__, dest);
if (dest[0] != MP_OBJ_NULL) {
// e.g. __path__ will be "<matched search path>/foo/bar"
vstr_add_str(&path, mp_obj_str_get_str(dest[0]));
// Add the level module name to the path to get "<matched search path>/foo/bar/baz".
vstr_add_char(&path, PATH_SEP_CHAR[0]);
vstr_add_str(&path, qstr_str(level_mod_name));
// Because it's not top level, we already know which path the parent was found in.
stat = stat_dir_or_file(path);
stat = stat_module(&path);
}
}
DEBUG_printf("Current path: %.*s\n", (int)vstr_len(path), vstr_str(path));
if (module_obj == MP_OBJ_NULL) {
// Not a built-in and not already-loaded.
// Not already loaded, and not a built-in, so look at the stat result from the filesystem/frozen.
if (stat == MP_IMPORT_STAT_NO_EXIST) {
// And the file wasn't found -- fail.
#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE
mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found"));
#else
mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), full_mod_name);
#endif
}
if (stat == MP_IMPORT_STAT_NO_EXIST) {
// Not found -- fail.
#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE
mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found"));
#else
mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), full_mod_name);
#endif
}
// Not a built-in but found on the filesystem, try and load it.
DEBUG_printf("Found path: %.*s\n", (int)vstr_len(path), vstr_str(path));
// Prepare for loading from the filesystem. Create a new shell module.
module_obj = mp_obj_new_module(full_mod_name);
#if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT
// If this module is being loaded via -m on unix, then
// override __name__ to "__main__". Do this only for *modules*
// however - packages never have their names replaced, instead
// they're -m'ed using a special __main__ submodule in them. (This all
// apparently is done to not touch the package name itself, which is
// important for future imports).
if (override_main && stat != MP_IMPORT_STAT_DIR) {
mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj);
mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__));
#if MICROPY_CPYTHON_COMPAT
// Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules).
mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj);
// Store real name in "__main__" attribute. Need this for
// resolving relative imports later. "__main__ was chosen
// semi-randonly, to reuse existing qstr's.
mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(full_mod_name));
#endif
}
#endif // MICROPY_MODULE_OVERRIDE_MAIN_IMPORT
if (stat == MP_IMPORT_STAT_DIR) {
// Directory -- execute "path/__init__.py".
DEBUG_printf("%.*s is dir\n", (int)vstr_len(path), vstr_str(path));
// Store the __path__ attribute onto this module.
// https://docs.python.org/3/reference/import.html
// "Specifically, any module that contains a __path__ attribute is considered a package."
mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(path), vstr_len(path)));
size_t orig_path_len = path->len;
vstr_add_str(path, PATH_SEP_CHAR "__init__.py");
if (stat_file_py_or_mpy(path) == MP_IMPORT_STAT_FILE) {
do_load(MP_OBJ_TO_PTR(module_obj), path);
} else {
// No-op. Nothing to load.
// mp_warning("%s is imported as namespace package", vstr_str(&path));
}
// Remove /__init__.py suffix.
path->len = orig_path_len;
} else { // MP_IMPORT_STAT_FILE
// File -- execute "path.(m)py".
do_load(MP_OBJ_TO_PTR(module_obj), path);
// Note: This should be the last component in the import path. If
// there are remaining components then it's an ImportError
// because the current path(the module that was just loaded) is
// not a package. This will be caught on the next iteration
// because the file will not exist.
// Module was found on the filesystem/frozen, try and load it.
DEBUG_printf("Found path to load: %.*s\n", (int)vstr_len(&path), vstr_str(&path));
// Prepare for loading from the filesystem. Create a new shell module.
module_obj = mp_obj_new_module(full_mod_name);
#if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT
// If this module is being loaded via -m on unix, then
// override __name__ to "__main__". Do this only for *modules*
// however - packages never have their names replaced, instead
// they're -m'ed using a special __main__ submodule in them. (This all
// apparently is done to not touch the package name itself, which is
// important for future imports).
if (override_main && stat != MP_IMPORT_STAT_DIR) {
mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj);
mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__));
#if MICROPY_CPYTHON_COMPAT
// Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules).
mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj);
// Store real name in "__main__" attribute. Need this for
// resolving relative imports later. "__main__ was chosen
// semi-randonly, to reuse existing qstr's.
mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(full_mod_name));
#endif
}
#endif // MICROPY_MODULE_OVERRIDE_MAIN_IMPORT
if (stat == MP_IMPORT_STAT_DIR) {
// Directory (i.e. a package).
DEBUG_printf("%.*s is dir\n", (int)vstr_len(&path), vstr_str(&path));
// Store the __path__ attribute onto this module.
// https://docs.python.org/3/reference/import.html
// "Specifically, any module that contains a __path__ attribute is considered a package."
// This gets used later to locate any subpackages of this module.
mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(&path), vstr_len(&path)));
size_t orig_path_len = path.len;
vstr_add_str(&path, PATH_SEP_CHAR "__init__.py");
// execute "path/__init__.py" (if available).
if (stat_file_py_or_mpy(&path) == MP_IMPORT_STAT_FILE) {
do_load(MP_OBJ_TO_PTR(module_obj), &path);
} else {
// No-op. Nothing to load.
// mp_warning("%s is imported as namespace package", vstr_str(&path));
}
// Remove /__init__.py suffix from path.
path.len = orig_path_len;
} else { // MP_IMPORT_STAT_FILE
// File -- execute "path.(m)py".
do_load(MP_OBJ_TO_PTR(module_obj), &path);
// Note: This should be the last component in the import path. If
// there are remaining components then in the next call to
// process_import_at_level will detect that it doesn't have
// a __path__ attribute, and not attempt to stat it.
}
if (outer_module_obj != MP_OBJ_NULL) {
// If it's a sub-module (not a built-in one), then make it available on
// the parent module.
// If it's a sub-module then make it available on the parent module.
mp_store_attr(outer_module_obj, level_mod_name, module_obj);
}
@ -504,7 +546,6 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) {
// i.e. "from . import foo" --> level=1
// i.e. "from ...foo.bar import baz" --> level=3
mp_int_t level = 0;
if (n_args >= 4) {
fromtuple = args[3];
if (n_args >= 5) {
@ -519,8 +560,10 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) {
const char *module_name = mp_obj_str_get_data(module_name_obj, &module_name_len);
if (level != 0) {
// Turn "foo.bar" into "<current module minus 3 components>.foo.bar".
// Turn "foo.bar" with level=3 into "<current module 3 components>.foo.bar".
// Current module name is extracted from globals().__name__.
evaluate_relative_import(level, &module_name, &module_name_len);
// module_name is now an absolute module path.
}
if (module_name_len == 0) {
@ -529,11 +572,12 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) {
DEBUG_printf("Starting module search for '%s'\n", module_name);
VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX)
mp_obj_t top_module_obj = MP_OBJ_NULL;
mp_obj_t outer_module_obj = MP_OBJ_NULL;
// Search for the end of each component.
// Iterate the absolute path, finding the end of each component of the path.
// foo.bar.baz
// ^ ^ ^
size_t current_component_start = 0;
for (size_t i = 1; i <= module_name_len; i++) {
if (i == module_name_len || module_name[i] == '.') {
@ -543,18 +587,18 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) {
qstr level_mod_name = qstr_from_strn(module_name + current_component_start, i - current_component_start);
DEBUG_printf("Processing module: '%s' at level '%s'\n", qstr_str(full_mod_name), qstr_str(level_mod_name));
DEBUG_printf("Previous path: =%.*s=\n", (int)vstr_len(&path), vstr_str(&path));
#if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT
// On unix, if this is being loaded via -m (magic mp_const_false),
// then handle that if it's the final component.
// On unix, if this is being loaded via -m (indicated by sentinel
// fromtuple=mp_const_false), then handle that if it's the final
// component.
bool override_main = (i == module_name_len && fromtuple == mp_const_false);
#else
bool override_main = false;
#endif
// Import this module.
mp_obj_t module_obj = process_import_at_level(full_mod_name, level_mod_name, outer_module_obj, &path, override_main);
mp_obj_t module_obj = process_import_at_level(full_mod_name, level_mod_name, outer_module_obj, override_main);
// Set this as the parent module, and remember the top-level module if it's the first.
outer_module_obj = module_obj;
@ -577,31 +621,38 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) {
#else // MICROPY_ENABLE_EXTERNAL_IMPORT
bool mp_obj_is_package(mp_obj_t module) {
return false;
}
mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) {
// Check that it's not a relative import
// Check that it's not a relative import.
if (n_args >= 5 && MP_OBJ_SMALL_INT_VALUE(args[4]) != 0) {
mp_raise_NotImplementedError(MP_ERROR_TEXT("relative import"));
}
// Check if module already exists, and return it if it does
// Check if the module is already loaded.
mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_loaded_modules_dict).map, args[0], MP_MAP_LOOKUP);
if (elem) {
return elem->value;
}
// Try the name directly as a built-in.
qstr module_name_qstr = mp_obj_str_get_qstr(args[0]);
mp_obj_t module_obj = mp_module_get_loaded_or_builtin(module_name_qstr);
mp_obj_t module_obj = mp_module_get_builtin(module_name_qstr);
if (module_obj != MP_OBJ_NULL) {
return module_obj;
}
#if MICROPY_MODULE_WEAK_LINKS
// Check if there is a weak link to this module
// Check if the u-prefixed name is a built-in.
VSTR_FIXED(umodule_path, MICROPY_ALLOC_PATH_MAX);
qstr umodule_name_qstr = make_weak_link_name(&umodule_path, module_name_qstr);
module_obj = mp_module_get_loaded_or_builtin(umodule_name_qstr);
module_obj = mp_module_get_builtin(umodule_name_qstr);
if (module_obj != MP_OBJ_NULL) {
return module_obj;
}
#elif MICROPY_PY_SYS
// Special handling to make `import sys` work even if weak links aren't enabled.
if (module_name_qstr == MP_QSTR_sys) {
return MP_OBJ_FROM_PTR(&mp_module_sys);
}
#endif
// Couldn't find the module, so fail

2
py/obj.h

@ -1201,8 +1201,6 @@ typedef struct _mp_obj_module_t {
static inline mp_obj_dict_t *mp_obj_module_get_globals(mp_obj_t module) {
return ((mp_obj_module_t *)MP_OBJ_TO_PTR(module))->globals;
}
// check if given module object is a package
bool mp_obj_is_package(mp_obj_t module);
// staticmethod and classmethod types; defined here so we can make const versions
// this structure is used for instances of both staticmethod and classmethod

32
py/objmodule.c

@ -176,35 +176,10 @@ STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = {
MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table);
// Tries to find a loaded module, otherwise attempts to load a builtin, otherwise MP_OBJ_NULL.
mp_obj_t mp_module_get_loaded_or_builtin(qstr module_name) {
// First try loaded modules.
mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_loaded_modules_dict).map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
if (!elem) {
#if MICROPY_MODULE_WEAK_LINKS
return mp_module_get_builtin(module_name);
#else
// Otherwise try builtin.
elem = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
if (!elem) {
return MP_OBJ_NULL;
}
#if MICROPY_MODULE_BUILTIN_INIT
// If found, it's a newly loaded built-in, so init it.
mp_module_call_init(MP_OBJ_NEW_QSTR(module_name), elem->value);
#endif
#endif
}
return elem->value;
}
#if MICROPY_MODULE_WEAK_LINKS
// Tries to find a loaded module, otherwise attempts to load a builtin, otherwise MP_OBJ_NULL.
// Attempts to find (and initialise) a builtin, otherwise returns MP_OBJ_NULL.
// This must only be called after first checking the loaded modules,
// otherwise the module will be re-initialised.
mp_obj_t mp_module_get_builtin(qstr module_name) {
// Try builtin.
mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
if (!elem) {
return MP_OBJ_NULL;
@ -217,7 +192,6 @@ mp_obj_t mp_module_get_builtin(qstr module_name) {
return elem->value;
}
#endif
#if MICROPY_MODULE_BUILTIN_INIT
STATIC void mp_module_register(mp_obj_t module_name, mp_obj_t module) {

3
py/objmodule.h

@ -33,10 +33,7 @@
extern const mp_map_t mp_builtin_module_map;
mp_obj_t mp_module_get_loaded_or_builtin(qstr module_name);
#if MICROPY_MODULE_WEAK_LINKS
mp_obj_t mp_module_get_builtin(qstr module_name);
#endif
void mp_module_generic_attr(qstr attr, mp_obj_t *dest, const uint16_t *keys, mp_obj_t *values);

3
py/runtime.c

@ -1531,7 +1531,8 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) {
#if MICROPY_ENABLE_EXTERNAL_IMPORT
// See if it's a package, then can try FS import
if (!mp_obj_is_package(module)) {
mp_load_method_maybe(module, MP_QSTR___path__, dest);
if (dest[0] == MP_OBJ_NULL) {
goto import_error;
}

Loading…
Cancel
Save