Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 696c35e

Browse files
committed
Issue #26186: Remove the restriction that built-in and extension
modules can't be lazily loaded. Thanks to Python 3.6 allowing for types.ModuleType to have its __class__ mutated, the restriction can be lifted by calling create_module() on the wrapped loader.
1 parent da03761 commit 696c35e

4 files changed

Lines changed: 50 additions & 27 deletions

File tree

Doc/library/importlib.rst

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,14 @@ ABC hierarchy::
379379

380380
An abstract method that executes the module in its own namespace
381381
when a module is imported or reloaded. The module should already
382-
be initialized when exec_module() is called.
382+
be initialized when ``exec_module()`` is called. When this method exists,
383+
:meth:`~importlib.abc.Loader.create_module` must be defined.
383384

384385
.. versionadded:: 3.4
385386

387+
.. versionchanged:: 3.6
388+
:meth:`~importlib.abc.Loader.create_module` must also be defined.
389+
386390
.. method:: load_module(fullname)
387391

388392
A legacy method for loading a module. If the module cannot be
@@ -1200,12 +1204,13 @@ an :term:`importer`.
12001204

12011205
.. function:: module_from_spec(spec)
12021206

1203-
Create a new module based on **spec** and ``spec.loader.create_module()``.
1207+
Create a new module based on **spec** and
1208+
:meth:`spec.loader.create_module <importlib.abc.Loader.create_module>`.
12041209

1205-
If ``spec.loader.create_module()`` does not return ``None``, then any
1206-
pre-existing attributes will not be reset. Also, no :exc:`AttributeError`
1207-
will be raised if triggered while accessing **spec** or setting an attribute
1208-
on the module.
1210+
If :meth:`spec.loader.create_module <importlib.abc.Loader.create_module>`
1211+
does not return ``None``, then any pre-existing attributes will not be reset.
1212+
Also, no :exc:`AttributeError` will be raised if triggered while accessing
1213+
**spec** or setting an attribute on the module.
12091214

12101215
This function is preferred over using :class:`types.ModuleType` to create a
12111216
new module as **spec** is used to set as many import-controlled attributes on
@@ -1267,7 +1272,8 @@ an :term:`importer`.
12671272

12681273
.. decorator:: set_package
12691274

1270-
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` to set the :attr:`__package__` attribute on the returned module. If :attr:`__package__`
1275+
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` to set the
1276+
:attr:`__package__` attribute on the returned module. If :attr:`__package__`
12711277
is set and has a value other than ``None`` it will not be changed.
12721278

12731279
.. deprecated:: 3.4
@@ -1300,13 +1306,12 @@ an :term:`importer`.
13001306
This class **only** works with loaders that define
13011307
:meth:`~importlib.abc.Loader.exec_module` as control over what module type
13021308
is used for the module is required. For those same reasons, the loader's
1303-
:meth:`~importlib.abc.Loader.create_module` method will be ignored (i.e., the
1304-
loader's method should only return ``None``; this excludes
1305-
:class:`BuiltinImporter` and :class:`ExtensionFileLoader`). Finally,
1306-
modules which substitute the object placed into :attr:`sys.modules` will
1307-
not work as there is no way to properly replace the module references
1308-
throughout the interpreter safely; :exc:`ValueError` is raised if such a
1309-
substitution is detected.
1309+
:meth:`~importlib.abc.Loader.create_module` method must return ``None`` or a
1310+
type for which its ``__class__`` attribute can be mutated along with not
1311+
using :term:`slots <__slots__>`. Finally, modules which substitute the object
1312+
placed into :attr:`sys.modules` will not work as there is no way to properly
1313+
replace the module references throughout the interpreter safely;
1314+
:exc:`ValueError` is raised if such a substitution is detected.
13101315

13111316
.. note::
13121317
For projects where startup time is critical, this class allows for
@@ -1317,6 +1322,11 @@ an :term:`importer`.
13171322

13181323
.. versionadded:: 3.5
13191324

1325+
.. versionchanged:: 3.6
1326+
Began calling :meth:`~importlib.abc.Loader.create_module`, removing the
1327+
compatibility warning for :class:`importlib.machinery.BuiltinImporter` and
1328+
:class:`importlib.machinery.ExtensionFileLoader`.
1329+
13201330
.. classmethod:: factory(loader)
13211331

13221332
A static method which returns a callable that creates a lazy loader. This

Doc/whatsnew/3.6.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,16 @@ The idlelib package is being modernized and refactored to make IDLE look and wor
291291
In compensation, the eventual result with be that some idlelib classes will be easier to use, with better APIs and docstrings explaining them. Additional useful information will be added to idlelib when available.
292292

293293

294+
importlib
295+
---------
296+
297+
:class:`importlib.util.LazyLoader` now calls
298+
:meth:`~importlib.abc.Loader.create_module` on the wrapped loader, removing the
299+
restriction that :class:`importlib.machinery.BuiltinImporter` and
300+
:class:`importlib.machinery.ExtensionFileLoader` couldn't be used with
301+
:class:`importlib.util.LazyLoader`.
302+
303+
294304
os
295305
--
296306

@@ -620,6 +630,9 @@ that may require changes to your code.
620630
Changes in the Python API
621631
-------------------------
622632

633+
* When :meth:`importlib.abc.Loader.exec_module` is defined,
634+
:meth:`importlib.abc.Loader.create_module` must also be defined.
635+
623636
* The format of the ``co_lnotab`` attribute of code objects changed to support
624637
negative line number delta. By default, Python does not emit bytecode with
625638
negative line number delta. Functions using ``frame.f_lineno``,

Lib/importlib/util.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,6 @@ def module_for_loader_wrapper(self, fullname, *args, **kwargs):
204204
return module_for_loader_wrapper
205205

206206

207-
class _Module(types.ModuleType):
208-
209-
"""A subclass of the module type to allow __class__ manipulation."""
210-
211-
212207
class _LazyModule(types.ModuleType):
213208

214209
"""A subclass of the module type which triggers loading upon attribute access."""
@@ -218,13 +213,14 @@ def __getattribute__(self, attr):
218213
# All module metadata must be garnered from __spec__ in order to avoid
219214
# using mutated values.
220215
# Stop triggering this method.
221-
self.__class__ = _Module
216+
self.__class__ = types.ModuleType
222217
# Get the original name to make sure no object substitution occurred
223218
# in sys.modules.
224219
original_name = self.__spec__.name
225220
# Figure out exactly what attributes were mutated between the creation
226221
# of the module and now.
227-
attrs_then = self.__spec__.loader_state
222+
attrs_then = self.__spec__.loader_state['__dict__']
223+
original_type = self.__spec__.loader_state['__class__']
228224
attrs_now = self.__dict__
229225
attrs_updated = {}
230226
for key, value in attrs_now.items():
@@ -239,9 +235,9 @@ def __getattribute__(self, attr):
239235
# object was put into sys.modules.
240236
if original_name in sys.modules:
241237
if id(self) != id(sys.modules[original_name]):
242-
msg = ('module object for {!r} substituted in sys.modules '
243-
'during a lazy load')
244-
raise ValueError(msg.format(original_name))
238+
raise ValueError(f"module object for {original_name!r} "
239+
"substituted in sys.modules during a lazy "
240+
"load")
245241
# Update after loading since that's what would happen in an eager
246242
# loading situation.
247243
self.__dict__.update(attrs_updated)
@@ -275,8 +271,7 @@ def __init__(self, loader):
275271
self.loader = loader
276272

277273
def create_module(self, spec):
278-
"""Create a module which can have its __class__ manipulated."""
279-
return _Module(spec.name)
274+
return self.loader.create_module(spec)
280275

281276
def exec_module(self, module):
282277
"""Make the module load lazily."""
@@ -286,5 +281,8 @@ def exec_module(self, module):
286281
# on an object would have triggered the load,
287282
# e.g. ``module.__spec__.loader = None`` would trigger a load from
288283
# trying to access module.__spec__.
289-
module.__spec__.loader_state = module.__dict__.copy()
284+
loader_state = {}
285+
loader_state['__dict__'] = module.__dict__.copy()
286+
loader_state['__class__'] = module.__class__
287+
module.__spec__.loader_state = loader_state
290288
module.__class__ = _LazyModule

Lib/test/test_importlib/test_lazy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def new_module(self, source_code=None):
6666
spec = util.spec_from_loader(TestingImporter.module_name,
6767
util.LazyLoader(loader))
6868
module = spec.loader.create_module(spec)
69+
if module is None:
70+
module = types.ModuleType(TestingImporter.module_name)
6971
module.__spec__ = spec
7072
module.__loader__ = spec.loader
7173
spec.loader.exec_module(module)

0 commit comments

Comments
 (0)