Skip to content

Commit 57db125

Browse files
QEDadyblurb-it[bot]brettcannon
authored
gh-139686: Make reloading a lazy module no-op (GH-139857)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Brett Cannon <[email protected]>
1 parent 8b669d5 commit 57db125

File tree

4 files changed

+36
-1
lines changed

4 files changed

+36
-1
lines changed

Doc/library/importlib.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ Functions
210210
:exc:`ModuleNotFoundError` is raised when the module being reloaded lacks
211211
a :class:`~importlib.machinery.ModuleSpec`.
212212

213+
.. versionchanged:: 3.14
214+
If *module* is a lazy module that has not yet been materialized (i.e.,
215+
loaded via :class:`importlib.util.LazyLoader` and not yet accessed),
216+
calling :func:`reload` is a no-op and returns the module unchanged.
217+
This prevents the reload from unintentionally triggering the lazy load.
218+
213219
.. warning::
214220
This function is not thread-safe. Calling it from multiple threads can result
215221
in unexpected behavior. It's recommended to use the :class:`threading.Lock`

Lib/importlib/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ def reload(module):
9797
The module must have been successfully imported before.
9898
9999
"""
100+
# If a LazyModule has not yet been materialized, reload is a no-op.
101+
if importlib_util := sys.modules.get('importlib.util'):
102+
if lazy_module_type := getattr(importlib_util, '_LazyModule', None):
103+
if isinstance(module, lazy_module_type):
104+
return module
100105
try:
101106
name = module.__spec__.name
102107
except AttributeError:

Lib/test/test_importlib/test_lazy.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from test.support import threading_helper
1111
from test.test_importlib import util as test_util
1212

13+
# Make sure sys.modules[util] is in sync with the import.
14+
# That is needed as other tests may reload util.
15+
sys.modules['importlib.util'] = util
1316

1417
class CollectInit:
1518

@@ -192,7 +195,7 @@ def test_lazy_self_referential_modules(self):
192195
sys.modules['json'] = module
193196
loader.exec_module(module)
194197

195-
# Trigger load with attribute lookup, ensure expected behavior
198+
# Trigger load with attribute lookup, ensure expected behavior.
196199
test_load = module.loads('{}')
197200
self.assertEqual(test_load, {})
198201

@@ -224,6 +227,26 @@ def __delattr__(self, name):
224227
with self.assertRaises(AttributeError):
225228
del module.CONSTANT
226229

230+
def test_reload(self):
231+
# Reloading a lazy module that hasn't been materialized is a no-op.
232+
module = self.new_module()
233+
sys.modules[TestingImporter.module_name] = module
234+
235+
# Change the source code to add a new attribute.
236+
TestingImporter.source_code = 'attr = 42\nnew_attr = 123\n__name__ = {!r}'.format(TestingImporter.mutated_name)
237+
self.assertIsInstance(module, util._LazyModule)
238+
239+
# Reload the module (should be a no-op since not materialized).
240+
reloaded = importlib.reload(module)
241+
self.assertIs(reloaded, module)
242+
self.assertIsInstance(module, util._LazyModule)
243+
244+
# Access the new attribute (should trigger materialization, and new_attr should exist).
245+
self.assertEqual(module.attr, 42)
246+
self.assertNotIsInstance(module, util._LazyModule)
247+
self.assertTrue(hasattr(module, 'new_attr'))
248+
self.assertEqual(module.new_attr, 123)
249+
227250

228251
if __name__ == '__main__':
229252
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make importlib.reload no-op for lazy modules.

0 commit comments

Comments
 (0)