Skip to content

Commit 881535b

Browse files
committed
Issue #14582: Import returns the module returned by a loader instead
of sys.modules when possible. This is being done for two reasons. One is to gain a little bit of performance by skipping an unnecessary dict lookup in sys.modules. But the other (and main) reason is to be a little bit more clear in how things should work from the perspective of import's interactions with loaders. Otherwise loaders can easily forget to return the module even though PEP 302 explicitly states they are expected to return the module they loaded.
1 parent 27fc528 commit 881535b

File tree

5 files changed

+494
-500
lines changed

5 files changed

+494
-500
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -974,12 +974,12 @@ def _find_and_load(name, import_):
974974
loader = _find_module(name, path)
975975
if loader is None:
976976
raise ImportError(_ERR_MSG.format(name), name=name)
977-
elif name not in sys.modules:
978-
# The parent import may have already imported this module.
979-
loader.load_module(name)
977+
elif name in sys.modules:
978+
# The parent module already imported this module.
979+
module = sys.modules[name]
980+
else:
981+
module = loader.load_module(name)
980982
verbose_message('import {!r} # {!r}', name, loader)
981-
# Backwards-compatibility; be nicer to skip the dict lookup.
982-
module = sys.modules[name]
983983
if parent:
984984
# Set the module as an attribute on its parent.
985985
parent_module = sys.modules[parent]
@@ -1078,7 +1078,11 @@ def __import__(name, globals={}, locals={}, fromlist=[], level=0):
10781078
# Return up to the first dot in 'name'. This is complicated by the fact
10791079
# that 'name' may be relative.
10801080
if level == 0:
1081-
return sys.modules[name.partition('.')[0]]
1081+
index = name.find('.')
1082+
if index == -1:
1083+
return module
1084+
else:
1085+
return sys.modules[name[:index]]
10821086
elif not name:
10831087
return module
10841088
else:

Lib/importlib/test/import_/test_caching.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,12 @@ def load_module(self, fullname):
4747
mock.load_module = MethodType(load_module, mock)
4848
return mock
4949

50-
# __import__ inconsistent between loaders and built-in import when it comes
51-
# to when to use the module in sys.modules and when not to.
52-
@import_util.importlib_only
53-
def test_using_cache_after_loader(self):
54-
# [from cache on return]
55-
with self.create_mock('module') as mock:
50+
def test_using_loader_return(self):
51+
loader_return = 'hi there!'
52+
with self.create_mock('module', return_=loader_return) as mock:
5653
with util.import_state(meta_path=[mock]):
5754
module = import_util.import_('module')
58-
self.assertEqual(id(module), id(sys.modules['module']))
59-
60-
# See test_using_cache_after_loader() for reasoning.
61-
@import_util.importlib_only
62-
def test_using_cache_for_assigning_to_attribute(self):
63-
# [from cache to attribute]
64-
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
65-
with util.import_state(meta_path=[importer]):
66-
module = import_util.import_('pkg.module')
67-
self.assertTrue(hasattr(module, 'module'))
68-
self.assertTrue(id(module.module), id(sys.modules['pkg.module']))
69-
70-
# See test_using_cache_after_loader() for reasoning.
71-
@import_util.importlib_only
72-
def test_using_cache_for_fromlist(self):
73-
# [from cache for fromlist]
74-
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
75-
with util.import_state(meta_path=[importer]):
76-
module = import_util.import_('pkg', fromlist=['module'])
77-
self.assertTrue(hasattr(module, 'module'))
78-
self.assertEqual(id(module.module),
79-
id(sys.modules['pkg.module']))
55+
self.assertEqual(module, loader_return)
8056

8157

8258
def test_main():

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3.0 Alpha 3?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #14582: Import directly returns the module as returned by a loader when
14+
possible instead of fetching it from sys.modules.
15+
1316
- Issue #13889: Check and (if necessary) set FPU control word before calling
1417
any of the dtoa.c string <-> float conversion functions, on MSVC builds of
1518
Python. This fixes issues when embedding Python in a Delphi app.

Python/import.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3019,15 +3019,22 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
30193019
Py_DECREF(partition);
30203020

30213021
if (level == 0) {
3022-
final_mod = PyDict_GetItem(interp->modules, front);
3023-
Py_DECREF(front);
3024-
if (final_mod == NULL) {
3025-
PyErr_Format(PyExc_KeyError,
3026-
"%R not in sys.modules as expected", front);
3022+
if (PyUnicode_GET_LENGTH(name) ==
3023+
PyUnicode_GET_LENGTH(front)) {
3024+
final_mod = mod;
30273025
}
30283026
else {
3029-
Py_INCREF(final_mod);
3027+
final_mod = PyDict_GetItem(interp->modules, front);
3028+
if (final_mod == NULL) {
3029+
PyErr_Format(PyExc_KeyError,
3030+
"%R not in sys.modules as expected", front);
3031+
}
3032+
}
3033+
Py_DECREF(front);
3034+
if (final_mod == NULL) {
3035+
goto error_with_unlock;
30303036
}
3037+
Py_INCREF(final_mod);
30313038
}
30323039
else {
30333040
Py_ssize_t cut_off = PyUnicode_GetLength(name) -

0 commit comments

Comments
 (0)