From 5ec7e46dbc9af60e6507893596f05518243b44d7 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 26 Feb 2020 15:24:09 +1100 Subject: [PATCH 1/2] py/builtinimport.c: Simplify module importing. Clear up the code path for importing: - already-loaded modules - built-in modules - built-in umodules (formerly weak links) - filesystem modules - (...and in the future, built-in packages) Also only enables the -m behavior with a (unix-only) flag. Code size reduction of XX. --- ports/unix/mpconfigport.h | 1 + py/builtinimport.c | 425 +++++++++++++++++++++----------------- py/mpconfig.h | 6 + py/objmodule.c | 63 +++--- py/objmodule.h | 14 +- 5 files changed, 270 insertions(+), 239 deletions(-) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index fc2e23106836c..4864b0692d421 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -81,6 +81,7 @@ #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1) #endif #define MICROPY_MODULE_WEAK_LINKS (1) +#define MICROPY_ENABLE_MODULE_OVERRIDE_MAIN (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) diff --git a/py/builtinimport.c b/py/builtinimport.c index b188f3c3489c9..9bbe2ea2d7955 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -5,6 +5,7 @@ * * Copyright (c) 2013-2019 Damien P. George * Copyright (c) 2014 Paul Sokolovsky + * Copyright (c) 2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -44,16 +45,17 @@ #define DEBUG_printf(...) (void)0 #endif -#if MICROPY_ENABLE_EXTERNAL_IMPORT - -#define PATH_SEP_CHAR '/' - 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; } +#if MICROPY_ENABLE_EXTERNAL_IMPORT + +#define PATH_SEP_CHAR "/" + +// Try and find exactly "foo/bar.py" (or "foo/bar.mpy"). // Stat either frozen or normal module by a given path // (whatever is available, if at all). STATIC mp_import_stat_t mp_import_stat_any(const char *path) { @@ -66,6 +68,7 @@ STATIC mp_import_stat_t mp_import_stat_any(const char *path) { return mp_import_stat(path); } +// Try and find "foo/bar.py", fall back to "foo/bar.mpy". STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); if (stat == MP_IMPORT_STAT_FILE) { @@ -73,6 +76,7 @@ STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { } #if MICROPY_PERSISTENT_CODE_LOAD + // Insert an 'm' into the '.py'. vstr_ins_byte(path, path->len - 2, 'm'); stat = mp_import_stat_any(vstr_null_terminated_str(path)); if (stat == MP_IMPORT_STAT_FILE) { @@ -83,6 +87,7 @@ STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { return MP_IMPORT_STAT_NO_EXIST; } +// Try and find "foo/bar" (a directory) or "foo/bar.(m)py". STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); DEBUG_printf("stat %s: %d\n", vstr_str(path), stat); @@ -95,7 +100,9 @@ STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { return stat_file_py_or_mpy(path); } -STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *dest) { +// This forwards to stat_dir_or_file, but tries in each location in sys.path. +STATIC mp_import_stat_t stat_dir_or_file_in_sys_path(qstr mod_name, vstr_t *dest) { + DEBUG_printf("stat_dir_or_file_in_sys_path: '%s'\n", qstr_str(mod_name)); #if MICROPY_PY_SYS // extract the list of paths size_t path_num; @@ -110,9 +117,9 @@ STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *d const char *p = mp_obj_str_get_data(path_items[i], &p_len); if (p_len > 0) { vstr_add_strn(dest, p, p_len); - vstr_add_char(dest, PATH_SEP_CHAR); + vstr_add_char(dest, PATH_SEP_CHAR[0]); } - vstr_add_strn(dest, file_str, file_len); + vstr_add_str(dest, qstr_str(mod_name)); mp_import_stat_t stat = stat_dir_or_file(dest); if (stat != MP_IMPORT_STAT_NO_EXIST) { return stat; @@ -125,7 +132,7 @@ STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *d #endif // mp_sys_path is empty, so just use the given file name - vstr_add_strn(dest, file_str, file_len); + vstr_add_str(dest, qstr_str(mod_name)); return stat_dir_or_file(dest); } @@ -232,15 +239,192 @@ STATIC void do_load(mp_obj_t module_obj, vstr_t *file) { #endif } -STATIC void chop_component(const char *start, const char **end) { - const char *p = *end; - while (p > start) { +STATIC void evaluate_relative_import(mp_int_t level, size_t *mod_len, const char **mod_str) { + // What we want to do here is to take name of current module, + // chop trailing components, and concatenate with the passed-in + // module name, thus resolving relative import name into absolute. + // This even appears to be correct per + // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name + // "Relative imports use a module's __name__ attribute to determine that + // module's position in the package hierarchy.". + + // For example, level=3, mod_str="foo.bar", __name__="a.b.c.d" --> "a.foo.bar" + + mp_obj_t this_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); + assert(this_name_obj != MP_OBJ_NULL); + #if MICROPY_ENABLE_MODULE_OVERRIDE_MAIN && MICROPY_CPYTHON_COMPAT + if (MP_OBJ_QSTR_VALUE(this_name_obj) == MP_QSTR___main__) { + // This is a module run by -m command-line switch, get its real name from backup attribute + this_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + } + #endif + mp_map_t *globals_map = &mp_globals_get()->map; + mp_map_elem_t *elem = mp_map_lookup(globals_map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); + bool is_pkg = (elem != NULL); + + #if DEBUG_PRINT + DEBUG_printf("Current module/package: "); + mp_obj_print(this_name_obj, PRINT_REPR); + DEBUG_printf(", is_package: %d", is_pkg); + DEBUG_printf("\n"); + #endif + + size_t this_name_l; + const char *this_name = mp_obj_str_get_data(this_name_obj, &this_name_l); + + const char *p = this_name + this_name_l; + if (is_pkg) { + // If it's relative to a package, then take off one fewer level. + --level; + } + + // Walk back 'level' dots. + while (level && p > this_name) { if (*--p == '.') { - *end = p; - return; + --level; } } - *end = p; + + // We must have some component left over to import from + if (p == this_name) { + mp_raise_ValueError("cannot perform relative import"); + } + + // New length is len("."). + // Might be one byte more than we need if mod_str is empty (for the extra .). + uint new_mod_l = (size_t)(p - this_name) + 1 + *mod_len; + char *new_mod = mp_local_alloc(new_mod_l); + memcpy(new_mod, this_name, p - this_name); + + // Only append "." if there was one). + if (*mod_len != 0) { + new_mod[p - this_name] = '.'; + memcpy(new_mod + (p - this_name) + 1, *mod_str, *mod_len); + } else { + --new_mod_l; + } + + // Copy into a QSTR. + qstr new_mod_q = qstr_from_strn(new_mod, new_mod_l); + mp_local_free(new_mod); + + DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); + *mod_str = qstr_str(new_mod_q); + *mod_len = new_mod_l; +} + +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; + + // Exact-match built-in (or already-loaded) takes priority. + mp_obj_t module_obj = mp_module_get_loaded_or_builtin(full_mod_name); + + // 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_level. + + // find the file corresponding to the module name + if (outer_module_obj == MP_OBJ_NULL) { + DEBUG_printf("Searching for top-level module\n"); + + // First module in the dotted-name; search for a directory or file. + // Because it's the top level, we try all sys.path entries. + stat = stat_dir_or_file_in_sys_path(full_mod_name, path); + + // If the module doesn't exist on the filesystem, and it's not a builtin, + // try the weak link instead. + #if MICROPY_MODULE_WEAK_LINKS + if (module_obj == MP_OBJ_NULL && stat == MP_IMPORT_STAT_NO_EXIST) { + char *umodule_buf = vstr_str(path); + umodule_buf[0] = 'u'; + strcpy(umodule_buf + 1, qstr_str(level_mod_name)); + qstr umodule_name = qstr_from_str(umodule_buf); + module_obj = mp_module_get_loaded_or_builtin(umodule_name); + } + #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)); + // Because it's not top level, we already know which path the parent was found in. + stat = stat_dir_or_file(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. + + 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, "module not found"); + } else { + mp_raise_msg_varg(&mp_type_ImportError, "no module named '%q'", full_mod_name); + } + } + + // 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_ENABLE_MODULE_OVERRIDE_MAIN + // if args[3] (fromtuple) has magic value False, set up + // this module for command-line "-m" option (set module's + // name to __main__ instead of real name). 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 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. 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_ENABLE_MODULE_OVERRIDE_MAIN + + if (stat == MP_IMPORT_STAT_DIR) { + // Directory -- execute "path/__init__.py". + DEBUG_printf("%.*s is dir\n", (int)vstr_len(path), vstr_str(path)); + // 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) { + //mp_warning("%s is imported as namespace package", vstr_str(&path)); + } else { + do_load(module_obj, path); + } + path->len = orig_path_len; + } else { // MP_IMPORT_STAT_FILE + // File -- execute "path.(m)py". + do_load(module_obj, path); + // 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. + } + } + + 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. + mp_store_attr(outer_module_obj, level_mod_name, module_obj); + } + + return module_obj; } mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { @@ -253,9 +437,22 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { } #endif + // This is the import path, with any leading dots stripped. + // "import foo.bar" --> module_name="foo.bar" + // "from foo.bar import baz" --> module_name="foo.bar" + // "from . import foo" --> module_name="" + // "from ...foo.bar import baz" --> module_name="foo.bar" mp_obj_t module_name = args[0]; + + // These are the imported names. + // i.e. "from foo.bar import baz, zap" --> fromtuple=("baz", "zap",) mp_obj_t fromtuple = mp_const_none; + + // Level is the number of leading dots in a relative import. + // 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) { @@ -270,204 +467,44 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { const char *mod_str = mp_obj_str_get_data(module_name, &mod_len); if (level != 0) { - // What we want to do here is to take name of current module, - // chop trailing components, and concatenate with passed-in - // module name, thus resolving relative import name into absolute. - // This even appears to be correct per - // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name - // "Relative imports use a module's __name__ attribute to determine that - // module's position in the package hierarchy." - level--; - mp_obj_t this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); - assert(this_name_q != MP_OBJ_NULL); - #if MICROPY_CPYTHON_COMPAT - if (MP_OBJ_QSTR_VALUE(this_name_q) == MP_QSTR___main__) { - // This is a module run by -m command-line switch, get its real name from backup attribute - this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); - } - #endif - mp_map_t *globals_map = &mp_globals_get()->map; - mp_map_elem_t *elem = mp_map_lookup(globals_map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); - bool is_pkg = (elem != NULL); - - #if DEBUG_PRINT - DEBUG_printf("Current module/package: "); - mp_obj_print(this_name_q, PRINT_REPR); - DEBUG_printf(", is_package: %d", is_pkg); - DEBUG_printf("\n"); - #endif - - size_t this_name_l; - const char *this_name = mp_obj_str_get_data(this_name_q, &this_name_l); - - const char *p = this_name + this_name_l; - if (!is_pkg) { - // We have module, but relative imports are anchored at package, so - // go there. - chop_component(this_name, &p); - } - - while (level--) { - chop_component(this_name, &p); - } - - // We must have some component left over to import from - if (p == this_name) { - mp_raise_ValueError("cannot perform relative import"); - } - - uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); - char *new_mod = mp_local_alloc(new_mod_l); - memcpy(new_mod, this_name, p - this_name); - if (mod_len != 0) { - new_mod[p - this_name] = '.'; - memcpy(new_mod + (p - this_name) + 1, mod_str, mod_len); - } - - qstr new_mod_q = qstr_from_strn(new_mod, new_mod_l); - mp_local_free(new_mod); - DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); - module_name = MP_OBJ_NEW_QSTR(new_mod_q); - mod_str = qstr_str(new_mod_q); - mod_len = new_mod_l; + // Turn "foo.bar" into ".foo.bar". + evaluate_relative_import(level, &mod_len, &mod_str); } if (mod_len == 0) { mp_raise_ValueError(NULL); } - // check if module already exists - qstr module_name_qstr = mp_obj_str_get_qstr(module_name); - mp_obj_t module_obj = mp_module_get(module_name_qstr); - if (module_obj != MP_OBJ_NULL) { - DEBUG_printf("Module already loaded\n"); - // If it's not a package, return module right away - char *p = strchr(mod_str, '.'); - if (p == NULL) { - return module_obj; - } - // If fromlist is not empty, return leaf module - if (fromtuple != mp_const_none) { - return module_obj; - } - // Otherwise, we need to return top-level package - qstr pkg_name = qstr_from_strn(mod_str, p - mod_str); - return mp_module_get(pkg_name); - } - DEBUG_printf("Module not yet loaded\n"); + DEBUG_printf("Starting module search for '%s'\n", mod_str); uint last = 0; VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX) - module_obj = MP_OBJ_NULL; mp_obj_t top_module_obj = MP_OBJ_NULL; mp_obj_t outer_module_obj = MP_OBJ_NULL; - uint i; - for (i = 1; i <= mod_len; i++) { + for (uint i = 1; i <= mod_len; i++) { if (i == mod_len || mod_str[i] == '.') { - // create a qstr for the module name up to this depth - qstr mod_name = qstr_from_strn(mod_str, i); - DEBUG_printf("Processing module: %s\n", qstr_str(mod_name)); - DEBUG_printf("Previous path: =%.*s=\n", vstr_len(&path), vstr_str(&path)); - - // find the file corresponding to the module name - mp_import_stat_t stat; - if (vstr_len(&path) == 0) { - // first module in the dotted-name; search for a directory or file - stat = find_file(mod_str, i, &path); - } else { - // latter module in the dotted-name; append to path - vstr_add_char(&path, PATH_SEP_CHAR); - vstr_add_strn(&path, mod_str + last, i - last); - stat = stat_dir_or_file(&path); - } - DEBUG_printf("Current path: %.*s\n", vstr_len(&path), vstr_str(&path)); - - if (stat == MP_IMPORT_STAT_NO_EXIST) { - module_obj = MP_OBJ_NULL; - #if MICROPY_MODULE_WEAK_LINKS - // check if there is a weak link to this module - if (i == mod_len) { - module_obj = mp_module_search_umodule(mod_str); - if (module_obj != MP_OBJ_NULL) { - // found weak linked module - mp_module_call_init(mod_name, module_obj); - } - } - #endif - if (module_obj == MP_OBJ_NULL) { - // couldn't find the file, so fail - if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { - mp_raise_msg(&mp_type_ImportError, "module not found"); - } else { - mp_raise_msg_varg(&mp_type_ImportError, "no module named '%q'", mod_name); - } - } - } else { - // found the file, so get the module - module_obj = mp_module_get(mod_name); - } + // The module name up to this depth (e.g. foo.bar.baz). + qstr full_mod_name = qstr_from_strn(mod_str, i); + // The current level name (e.g. baz). + qstr level_mod_name = qstr_from_strn(mod_str + last, i - last); + + 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)); + + mp_obj_t module_obj = process_import_at_level(full_mod_name, level_mod_name, outer_module_obj, &path, i == mod_len && fromtuple == mp_const_false); - if (module_obj == MP_OBJ_NULL) { - // module not already loaded, so load it! - - module_obj = mp_obj_new_module(mod_name); - - // if args[3] (fromtuple) has magic value False, set up - // this module for command-line "-m" option (set module's - // name to __main__ instead of real name). 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 package name itself, which is important for future - // imports). - if (i == mod_len && fromtuple == mp_const_false && 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. 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(mod_name)); - #endif - } - - if (stat == MP_IMPORT_STAT_DIR) { - DEBUG_printf("%.*s is dir\n", vstr_len(&path), vstr_str(&path)); - // 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_char(&path, PATH_SEP_CHAR); - vstr_add_str(&path, "__init__.py"); - if (stat_file_py_or_mpy(&path) != MP_IMPORT_STAT_FILE) { - //mp_warning("%s is imported as namespace package", vstr_str(&path)); - } else { - do_load(module_obj, &path); - } - path.len = orig_path_len; - } else { // MP_IMPORT_STAT_FILE - do_load(module_obj, &path); - // 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. - } - } - if (outer_module_obj != MP_OBJ_NULL) { - qstr s = qstr_from_strn(mod_str + last, i - last); - mp_store_attr(outer_module_obj, s, module_obj); - } outer_module_obj = module_obj; if (top_module_obj == MP_OBJ_NULL) { top_module_obj = module_obj; } + last = i + 1; } } // If fromlist is not empty, return leaf module if (fromtuple != mp_const_none) { - return module_obj; + return outer_module_obj; } // Otherwise, we need to return top-level package return top_module_obj; @@ -483,17 +520,19 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { // Check if module already exists, and return it if it does qstr module_name_qstr = mp_obj_str_get_qstr(args[0]); - mp_obj_t module_obj = mp_module_get(module_name_qstr); + mp_obj_t module_obj = mp_module_get_loaded_or_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 - module_obj = mp_module_search_umodule(qstr_str(module_name_qstr)); + char umodule_buf[MICROPY_ALLOC_PATH_MAX]; + umodule_buf[0] = 'u'; + strcpy(umodule_buf + 1, args[0]); + qstr umodule_name_qstr = qstr_from_str(umodule_buf); + module_obj = mp_module_get_loaded_or_builtin(umodule_name_qstr); if (module_obj != MP_OBJ_NULL) { - // Found weak-linked module - mp_module_call_init(module_name_qstr, module_obj); return module_obj; } #endif diff --git a/py/mpconfig.h b/py/mpconfig.h index 4cefdf31d6ebc..e0a9120f55287 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -487,6 +487,12 @@ #define MICROPY_ENABLE_EXTERNAL_IMPORT (1) #endif +// Whether to enable importing foo.py with __name__ set to '__main__' +// Used by the unix port for the -m flag. +#ifndef MICROPY_ENABLE_MODULE_OVERRIDE_MAIN +#define MICROPY_ENABLE_MODULE_OVERRIDE_MAIN (0) +#endif + // Whether to use the POSIX reader for importing files #ifndef MICROPY_READER_POSIX #define MICROPY_READER_POSIX (0) diff --git a/py/objmodule.c b/py/objmodule.c index 79047009f6d2d..851f421ad0f6a 100644 --- a/py/objmodule.c +++ b/py/objmodule.c @@ -35,6 +35,10 @@ #include "genhdr/moduledefs.h" +#if MICROPY_MODULE_BUILTIN_INIT +STATIC void mp_module_call_init(mp_obj_t module_name, mp_obj_t module_obj); +#endif + STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); @@ -239,47 +243,40 @@ STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = { MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table); -// returns MP_OBJ_NULL if not found -mp_obj_t mp_module_get(qstr module_name) { - mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; - // lookup module - mp_map_elem_t *el = mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); - - if (el == NULL) { - // module not found, look for builtin module names - el = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); - if (el == NULL) { - return MP_OBJ_NULL; - } - mp_module_call_init(module_name, el->value); - } - - // module found, return it - return el->value; +STATIC mp_obj_t lookup_module(mp_map_t *map, mp_obj_t key) { + mp_map_elem_t *el = mp_map_lookup(map, key, MP_MAP_LOOKUP); + return el ? el->value : MP_OBJ_NULL; } -void mp_module_register(qstr qst, mp_obj_t module) { - mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; - mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module; -} +// 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_obj_t result = lookup_module(&MP_STATE_VM(mp_loaded_modules_dict).map, MP_OBJ_NEW_QSTR(module_name)); -#if MICROPY_MODULE_WEAK_LINKS -// Search for u"foo" in built-in modules, return MP_OBJ_NULL if not found -mp_obj_t mp_module_search_umodule(const char *module_str) { - for (size_t i = 0; i < MP_ARRAY_SIZE(mp_builtin_module_table); ++i) { - const mp_map_elem_t *entry = (const mp_map_elem_t *)&mp_builtin_module_table[i]; - const char *key = qstr_str(MP_OBJ_QSTR_VALUE(entry->key)); - if (key[0] == 'u' && strcmp(&key[1], module_str) == 0) { - return (mp_obj_t)entry->value; - } + if (result != MP_OBJ_NULL) { + return result; + } + + // Otherwise try builtin. + result = lookup_module((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name)); + #if MICROPY_MODULE_BUILTIN_INIT + if (result != MP_OBJ_NULL) { + // If found, it's a newly loaded built, so init it. + mp_module_call_init(MP_OBJ_NEW_QSTR(module_name), result); } - return MP_OBJ_NULL; + #endif + + return result; } -#endif #if MICROPY_MODULE_BUILTIN_INIT -void mp_module_call_init(qstr module_name, mp_obj_t module_obj) { +STATIC void mp_module_register(mp_obj_t module_name, mp_obj_t module) { + mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; + mp_map_lookup(mp_loaded_modules_map, module_name, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module; +} + +STATIC void mp_module_call_init(mp_obj_t module_name, mp_obj_t module_obj) { // Look for __init__ and call it if it exists mp_obj_t dest[2]; mp_load_method_maybe(module_obj, MP_QSTR___init__, dest); diff --git a/py/objmodule.h b/py/objmodule.h index fde4fff34e0ef..c344f7b8ecc12 100644 --- a/py/objmodule.h +++ b/py/objmodule.h @@ -30,18 +30,6 @@ extern const mp_map_t mp_builtin_module_map; -mp_obj_t mp_module_get(qstr module_name); -void mp_module_register(qstr qstr, mp_obj_t module); - -mp_obj_t mp_module_search_umodule(const char *module_str); - -#if MICROPY_MODULE_BUILTIN_INIT -void mp_module_call_init(qstr module_name, mp_obj_t module_obj); -#else -static inline void mp_module_call_init(qstr module_name, mp_obj_t module_obj) { - (void)module_name; - (void)module_obj; -} -#endif +mp_obj_t mp_module_get_loaded_or_builtin(qstr module_name); #endif // MICROPY_INCLUDED_PY_OBJMODULE_H From 9e7c3e75dcf68b44457c4fc80663889ad89b04d8 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 26 Feb 2020 18:51:43 +1100 Subject: [PATCH 2/2] py/builtinimport.c: Add support for built-in packages. Allows a built-in module to add a sub-module as a member of its globals dict, and allow importing it as "import foo.bar" (or any other variation on the import statement). Note: This does give the slightly surprising behavior that "import foo" followed by "foo.bar.x" will also work. +56 bytes on PYBV11. --- py/builtinimport.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/py/builtinimport.c b/py/builtinimport.c index 9bbe2ea2d7955..f9d6fed86bbe6 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -315,6 +315,7 @@ STATIC void evaluate_relative_import(mp_int_t level, size_t *mod_len, const char 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; + bool store_on_parent = true; // Exact-match built-in (or already-loaded) takes priority. mp_obj_t module_obj = mp_module_get_loaded_or_builtin(full_mod_name); @@ -346,6 +347,21 @@ STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, } else { DEBUG_printf("Searching for sub-module\n"); + if (module_obj == MP_OBJ_NULL) { + // See if the outer module has this module as an attr. + // e.g. built-in modules can add modules to their globals dict + // to provide something that behaves like built-in packages. + mp_obj_t dest[2] = { MP_OBJ_NULL }; + mp_type_module.attr(outer_module_obj, level_mod_name, dest); + if (dest[0] != MP_OBJ_NULL) { + // Ensure that it's actually a module. + if (mp_obj_get_type(dest[0]) == &mp_type_module) { + module_obj = dest[0]; + store_on_parent = false; + } + } + } + // 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)); @@ -418,7 +434,7 @@ STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, } } - if (outer_module_obj != MP_OBJ_NULL) { + if (store_on_parent && 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. mp_store_attr(outer_module_obj, level_mod_name, module_obj); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy