Skip to content

Commit 2de5097

Browse files
authored
bpo-26131: Deprecate usage of load_module() (GH-23469)
Raise an ImportWarning when the import system falls back on load_module(). As for implementations of load_module(), raise a DeprecationWarning.
1 parent 79c1849 commit 2de5097

25 files changed

+3244
-3033
lines changed

Doc/whatsnew/3.10.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ Optimizations
394394
with ``gcc`` by up to 30%. See `this article
395395
<https://developers.redhat.com/blog/2020/06/25/red-hat-enterprise-linux-8-2-brings-faster-python-3-8-run-speeds/>`_
396396
for more details. (Contributed by Victor Stinner and Pablo Galindo in
397-
:issue:`38980`)
397+
:issue:`38980`.)
398+
398399

399400
* Function parameters and their annotations are no longer computed at runtime,
400401
but rather at compilation time. They are stored as a tuple of strings at the
@@ -421,6 +422,21 @@ Deprecated
421422
as appropriate to help identify code which needs updating during
422423
this transition.
423424

425+
* The various ``load_module()`` methods of :mod:`importlib` have been
426+
documented as deprecated since Python 3.6, but will now also trigger
427+
a :exc:`DeprecationWarning`. Use
428+
:meth:`~importlib.abc.Loader.exec_module` instead.
429+
(Contributed by Brett Cannon in :issue:`26131`.)
430+
431+
* :meth:`zimport.zipimporter.load_module` has been deprecated in
432+
preference for :meth:`~zipimport.zipimporter.exec_module`.
433+
(Contributed by Brett Cannon in :issue:`26131`.)
434+
435+
* The use of :meth:`~importlib.abc.Loader.load_module` by the import
436+
system now triggers an :exc:`ImportWarning` as
437+
:meth:`~importlib.abc.Loader.exec_module` is preferred.
438+
(Contributed by Brett Cannon in :issue:`26131`.)
439+
424440
* ``sqlite3.OptimizedUnicode`` has been undocumented and obsolete since Python
425441
3.3, when it was made an alias to :class:`str`. It is now deprecated,
426442
scheduled for removal in Python 3.12.

Lib/importlib/_abc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def load_module(self, fullname):
3535
"""
3636
if not hasattr(self, 'exec_module'):
3737
raise ImportError
38+
# Warning implemented in _load_module_shim().
3839
return _bootstrap._load_module_shim(self, fullname)
3940

4041
def module_repr(self, module):

Lib/importlib/_bootstrap.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
# reference any injected objects! This includes not only global code but also
2121
# anything specified at the class level.
2222

23+
def _object_name(obj):
24+
try:
25+
return obj.__qualname__
26+
except AttributeError:
27+
return type(obj).__qualname__
28+
2329
# Bootstrap-related code ######################################################
2430

2531
# Modules injected manually by _setup()
@@ -272,6 +278,9 @@ def _load_module_shim(self, fullname):
272278
This method is deprecated. Use loader.exec_module instead.
273279
274280
"""
281+
msg = ("the load_module() method is deprecated and slated for removal in "
282+
"Python 3.12; use exec_module() instead")
283+
_warnings.warn(msg, DeprecationWarning)
275284
spec = spec_from_loader(fullname, self)
276285
if fullname in sys.modules:
277286
module = sys.modules[fullname]
@@ -612,9 +621,9 @@ def _exec(spec, module):
612621
else:
613622
_init_module_attrs(spec, module, override=True)
614623
if not hasattr(spec.loader, 'exec_module'):
615-
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
616-
# have exec_module() implemented, we can add a deprecation
617-
# warning here.
624+
msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
625+
"falling back to load_module()")
626+
_warnings.warn(msg, ImportWarning)
618627
spec.loader.load_module(name)
619628
else:
620629
spec.loader.exec_module(module)
@@ -627,9 +636,8 @@ def _exec(spec, module):
627636

628637

629638
def _load_backward_compatible(spec):
630-
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
631-
# have exec_module() implemented, we can add a deprecation
632-
# warning here.
639+
# It is assumed that all callers have been warned about using load_module()
640+
# appropriately before calling this function.
633641
try:
634642
spec.loader.load_module(spec.name)
635643
except:
@@ -668,6 +676,9 @@ def _load_unlocked(spec):
668676
if spec.loader is not None:
669677
# Not a namespace package.
670678
if not hasattr(spec.loader, 'exec_module'):
679+
msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
680+
"falling back to load_module()")
681+
_warnings.warn(msg, ImportWarning)
671682
return _load_backward_compatible(spec)
672683

673684
module = module_from_spec(spec)
@@ -851,6 +862,7 @@ def load_module(cls, fullname):
851862
This method is deprecated. Use exec_module() instead.
852863
853864
"""
865+
# Warning about deprecation implemented in _load_module_shim().
854866
return _load_module_shim(cls, fullname)
855867

856868
@classmethod

Lib/importlib/_bootstrap_external.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,8 @@ def exec_module(self, module):
832832
_bootstrap._call_with_frames_removed(exec, code, module.__dict__)
833833

834834
def load_module(self, fullname):
835-
"""This module is deprecated."""
835+
"""This method is deprecated."""
836+
# Warning implemented in _load_module_shim().
836837
return _bootstrap._load_module_shim(self, fullname)
837838

838839

@@ -1007,7 +1008,7 @@ def load_module(self, fullname):
10071008
"""
10081009
# The only reason for this method is for the name check.
10091010
# Issue #14857: Avoid the zero-argument form of super so the implementation
1010-
# of that form can be updated without breaking the frozen module
1011+
# of that form can be updated without breaking the frozen module.
10111012
return super(FileLoader, self).load_module(fullname)
10121013

10131014
@_check_name
@@ -1253,6 +1254,7 @@ def load_module(self, fullname):
12531254
# The import system never calls this method.
12541255
_bootstrap._verbose_message('namespace module loaded with path {!r}',
12551256
self._path)
1257+
# Warning implemented in _load_module_shim().
12561258
return _bootstrap._load_module_shim(self, fullname)
12571259

12581260

Lib/test/test_importlib/builtin/test_loader.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import types
88
import unittest
9+
import warnings
910

1011
@unittest.skipIf(util.BUILTINS.good_name is None, 'no reasonable builtin module')
1112
class LoaderTests(abc.LoaderTests):
@@ -24,7 +25,9 @@ def verify(self, module):
2425
self.assertIn(module.__name__, sys.modules)
2526

2627
def load_module(self, name):
27-
return self.machinery.BuiltinImporter.load_module(name)
28+
with warnings.catch_warnings():
29+
warnings.simplefilter("ignore", DeprecationWarning)
30+
return self.machinery.BuiltinImporter.load_module(name)
2831

2932
def test_module(self):
3033
# Common case.

Lib/test/test_importlib/extension/test_loader.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from warnings import catch_warnings
12
from .. import abc
23
from .. import util
34

@@ -7,6 +8,7 @@
78
import sys
89
import types
910
import unittest
11+
import warnings
1012
import importlib.util
1113
import importlib
1214
from test.support.script_helper import assert_python_failure
@@ -20,14 +22,18 @@ def setUp(self):
2022
util.EXTENSIONS.file_path)
2123

2224
def load_module(self, fullname):
23-
return self.loader.load_module(fullname)
25+
with warnings.catch_warnings():
26+
warnings.simplefilter("ignore", DeprecationWarning)
27+
return self.loader.load_module(fullname)
2428

2529
def test_load_module_API(self):
2630
# Test the default argument for load_module().
27-
self.loader.load_module()
28-
self.loader.load_module(None)
29-
with self.assertRaises(ImportError):
30-
self.load_module('XXX')
31+
with warnings.catch_warnings():
32+
warnings.simplefilter("ignore", DeprecationWarning)
33+
self.loader.load_module()
34+
self.loader.load_module(None)
35+
with self.assertRaises(ImportError):
36+
self.load_module('XXX')
3137

3238
def test_equality(self):
3339
other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
@@ -94,6 +100,21 @@ def setUp(self):
94100
self.loader = self.machinery.ExtensionFileLoader(
95101
self.name, self.spec.origin)
96102

103+
def load_module(self):
104+
'''Load the module from the test extension'''
105+
with warnings.catch_warnings():
106+
warnings.simplefilter("ignore", DeprecationWarning)
107+
return self.loader.load_module(self.name)
108+
109+
def load_module_by_name(self, fullname):
110+
'''Load a module from the test extension by name'''
111+
origin = self.spec.origin
112+
loader = self.machinery.ExtensionFileLoader(fullname, origin)
113+
spec = importlib.util.spec_from_loader(fullname, loader)
114+
module = importlib.util.module_from_spec(spec)
115+
loader.exec_module(module)
116+
return module
117+
97118
# No extension module as __init__ available for testing.
98119
test_package = None
99120

@@ -157,19 +178,6 @@ def test_try_registration(self):
157178
with self.assertRaises(SystemError):
158179
module.call_state_registration_func(2)
159180

160-
def load_module(self):
161-
'''Load the module from the test extension'''
162-
return self.loader.load_module(self.name)
163-
164-
def load_module_by_name(self, fullname):
165-
'''Load a module from the test extension by name'''
166-
origin = self.spec.origin
167-
loader = self.machinery.ExtensionFileLoader(fullname, origin)
168-
spec = importlib.util.spec_from_loader(fullname, loader)
169-
module = importlib.util.module_from_spec(spec)
170-
loader.exec_module(module)
171-
return module
172-
173181
def test_load_submodule(self):
174182
'''Test loading a simulated submodule'''
175183
module = self.load_module_by_name('pkg.' + self.name)

Lib/test/test_importlib/frozen/test_loader.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,19 +161,23 @@ def test_module_repr(self):
161161
"<module '__hello__' (frozen)>")
162162

163163
def test_module_repr_indirect(self):
164-
with util.uncache('__hello__'), captured_stdout():
165-
module = self.machinery.FrozenImporter.load_module('__hello__')
166-
self.assertEqual(repr(module),
167-
"<module '__hello__' (frozen)>")
164+
with warnings.catch_warnings():
165+
warnings.simplefilter("ignore", DeprecationWarning)
166+
with util.uncache('__hello__'), captured_stdout():
167+
module = self.machinery.FrozenImporter.load_module('__hello__')
168+
self.assertEqual(repr(module),
169+
"<module '__hello__' (frozen)>")
168170

169171
# No way to trigger an error in a frozen module.
170172
test_state_after_failure = None
171173

172174
def test_unloadable(self):
173-
assert self.machinery.FrozenImporter.find_module('_not_real') is None
174-
with self.assertRaises(ImportError) as cm:
175-
self.machinery.FrozenImporter.load_module('_not_real')
176-
self.assertEqual(cm.exception.name, '_not_real')
175+
with warnings.catch_warnings():
176+
warnings.simplefilter("ignore", DeprecationWarning)
177+
assert self.machinery.FrozenImporter.find_module('_not_real') is None
178+
with self.assertRaises(ImportError) as cm:
179+
self.machinery.FrozenImporter.load_module('_not_real')
180+
self.assertEqual(cm.exception.name, '_not_real')
177181

178182

179183
(Frozen_LoaderTests,

Lib/test/test_importlib/import_/test___loader__.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
import types
44
import unittest
5+
import warnings
56

67
from .. import util
78

@@ -45,25 +46,29 @@ def load_module(self, fullname):
4546
class LoaderAttributeTests:
4647

4748
def test___loader___missing(self):
48-
module = types.ModuleType('blah')
49-
try:
50-
del module.__loader__
51-
except AttributeError:
52-
pass
53-
loader = LoaderMock()
54-
loader.module = module
55-
with util.uncache('blah'), util.import_state(meta_path=[loader]):
56-
module = self.__import__('blah')
57-
self.assertEqual(loader, module.__loader__)
49+
with warnings.catch_warnings():
50+
warnings.simplefilter("ignore", ImportWarning)
51+
module = types.ModuleType('blah')
52+
try:
53+
del module.__loader__
54+
except AttributeError:
55+
pass
56+
loader = LoaderMock()
57+
loader.module = module
58+
with util.uncache('blah'), util.import_state(meta_path=[loader]):
59+
module = self.__import__('blah')
60+
self.assertEqual(loader, module.__loader__)
5861

5962
def test___loader___is_None(self):
60-
module = types.ModuleType('blah')
61-
module.__loader__ = None
62-
loader = LoaderMock()
63-
loader.module = module
64-
with util.uncache('blah'), util.import_state(meta_path=[loader]):
65-
returned_module = self.__import__('blah')
66-
self.assertEqual(loader, module.__loader__)
63+
with warnings.catch_warnings():
64+
warnings.simplefilter("ignore", ImportWarning)
65+
module = types.ModuleType('blah')
66+
module.__loader__ = None
67+
loader = LoaderMock()
68+
loader.module = module
69+
with util.uncache('blah'), util.import_state(meta_path=[loader]):
70+
returned_module = self.__import__('blah')
71+
self.assertEqual(loader, module.__loader__)
6772

6873

6974
(Frozen_Tests,

Lib/test/test_importlib/import_/test___package__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ def __init__(self, parent):
9898
class Using__package__PEP302(Using__package__):
9999
mock_modules = util.mock_modules
100100

101+
def test_using___package__(self):
102+
with warnings.catch_warnings():
103+
warnings.simplefilter("ignore", ImportWarning)
104+
super().test_using___package__()
105+
106+
def test_spec_fallback(self):
107+
with warnings.catch_warnings():
108+
warnings.simplefilter("ignore", ImportWarning)
109+
super().test_spec_fallback()
110+
101111

102112
(Frozen_UsingPackagePEP302,
103113
Source_UsingPackagePEP302
@@ -155,6 +165,21 @@ def test_submodule(self):
155165
class Setting__package__PEP302(Setting__package__, unittest.TestCase):
156166
mock_modules = util.mock_modules
157167

168+
def test_top_level(self):
169+
with warnings.catch_warnings():
170+
warnings.simplefilter("ignore", ImportWarning)
171+
super().test_top_level()
172+
173+
def test_package(self):
174+
with warnings.catch_warnings():
175+
warnings.simplefilter("ignore", ImportWarning)
176+
super().test_package()
177+
178+
def test_submodule(self):
179+
with warnings.catch_warnings():
180+
warnings.simplefilter("ignore", ImportWarning)
181+
super().test_submodule()
182+
158183
class Setting__package__PEP451(Setting__package__, unittest.TestCase):
159184
mock_modules = util.mock_spec
160185

0 commit comments

Comments
 (0)