From ca9363efd5a79b59d7f2638473d26a16aaea449e Mon Sep 17 00:00:00 2001 From: Thanos <111999343+Sachaa-Thanasius@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:07:42 -0500 Subject: [PATCH 1/3] Update `importlib.util._LazyModule`. Allow `importlib.util._LazyModule` to special-case its `__spec__` attribute so that the regular import machinery requesting that attribute doesn't trigger the full module load. --- Lib/importlib/util.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 2b564e9b52e0cb..531ae12eb84b36 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -171,6 +171,13 @@ class _LazyModule(types.ModuleType): def __getattribute__(self, attr): """Trigger the load of the module and return the attribute.""" __spec__ = object.__getattribute__(self, '__spec__') + + # Allow __spec__ to go through without triggering the load, since the import + # machinery uses it to determine if a cached module is still initialzing + # (see importlib._bootstrap._find_and_load()). + if attr == "__spec__": + return __spec__ + loader_state = __spec__.loader_state with loader_state['lock']: # Only the first thread to get the lock should trigger the load From 173b211509f96f9adf6a29433f3f81db83dd69a7 Mon Sep 17 00:00:00 2001 From: Thanos <111999343+Sachaa-Thanasius@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:15:49 -0500 Subject: [PATCH 2/3] Trim trailing whitespace --- Lib/importlib/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 531ae12eb84b36..8652ee495d58f7 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -177,7 +177,7 @@ def __getattribute__(self, attr): # (see importlib._bootstrap._find_and_load()). if attr == "__spec__": return __spec__ - + loader_state = __spec__.loader_state with loader_state['lock']: # Only the first thread to get the lock should trigger the load From 60bdbaf3f54f5f8074d36ac0e2e2f52c58f78988 Mon Sep 17 00:00:00 2001 From: Thanos <111999343+Sachaa-Thanasius@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:59:14 -0500 Subject: [PATCH 3/3] Attempt to add tests. One test added and one modified. --- Lib/test/test_importlib/test_lazy.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Lib/test/test_importlib/test_lazy.py b/Lib/test/test_importlib/test_lazy.py index 5c6e0303528906..360e76e1634a2b 100644 --- a/Lib/test/test_importlib/test_lazy.py +++ b/Lib/test/test_importlib/test_lazy.py @@ -90,6 +90,11 @@ def test_e2e(self): with test_util.import_state(meta_path=[importer]): module = importlib.import_module(importer.module_name) self.assertIsNone(importer.loaded) + + # Check that getting __spec__ doesn't trigger the load. + module.__spec__ + self.assertIsNone(importer.loaded) + # Trigger load. self.assertEqual(module.__loader__, importer) self.assertIsNotNone(importer.loaded) @@ -224,6 +229,21 @@ def __delattr__(self, name): with self.assertRaises(AttributeError): del module.CONSTANT + def test_spec_passthrough(self): + # Verify that a lazily loaded module can have its __spec__ retrieved without triggering + # a full load. That way, importlib internal machinery will not cause a load when pulling + # a lazily loaded module out of sys.modules. + loader = TestingImporter() + module = self.new_module(loader=loader) + + # Access __spec__ without triggering the load. + _ = module.__spec__ + self.assertIsNone(loader.loaded) + + # Trigger the full load. + _ = module.attr + self.assertIsNotNone(loader.loaded) + if __name__ == '__main__': unittest.main()