Kaydet (Commit) 3dc48d6f authored tarafından Brett Cannon's avatar Brett Cannon

Issue #18070: importlib.util.module_for_loader() now sets __loader__

and __package__ unconditionally in order to do the right thing for
reloading.
üst a22faca7
...@@ -811,12 +811,11 @@ an :term:`importer`. ...@@ -811,12 +811,11 @@ an :term:`importer`.
The decorated method will take in the **name** of the module to be loaded The decorated method will take in the **name** of the module to be loaded
as expected for a :term:`loader`. If the module is not found in as expected for a :term:`loader`. If the module is not found in
:data:`sys.modules` then a new one is constructed with its :data:`sys.modules` then a new one is constructed. Regardless of where the
:attr:`__name__` attribute set to **name**, :attr:`__loader__` set to module came from, :attr:`__loader__` set to **self** and :attr:`__package__`
**self**, and :attr:`__package__` set based on what is set based on what :meth:`importlib.abc.InspectLoader.is_package` returns
:meth:`importlib.abc.InspectLoader.is_package` returns (if available). If a (if available). These attributes are set unconditionally to support
new module is not needed then the module found in :data:`sys.modules` will reloading.
be passed into the method.
If an exception is raised by the decorated method and a module was added to If an exception is raised by the decorated method and a module was added to
:data:`sys.modules` it will be removed to prevent a partially initialized :data:`sys.modules` it will be removed to prevent a partially initialized
...@@ -831,6 +830,10 @@ an :term:`importer`. ...@@ -831,6 +830,10 @@ an :term:`importer`.
:attr:`__loader__` and :attr:`__package__` are automatically set :attr:`__loader__` and :attr:`__package__` are automatically set
(when possible). (when possible).
.. versionchanged:: 3.4
Set :attr:`__loader__` :attr:`__package__` unconditionally to support
reloading.
.. decorator:: set_loader .. decorator:: set_loader
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
...@@ -843,12 +846,13 @@ an :term:`importer`. ...@@ -843,12 +846,13 @@ an :term:`importer`.
.. note:: .. note::
As this decorator sets :attr:`__loader__` after loading the module, it is As this decorator sets :attr:`__loader__` after loading the module, it is
recommended to use :func:`module_for_loader` instead when appropriate. recommended to use :func:`module_for_loader` instead when appropriate.
This decorator is also redundant as of Python 3.3 as import itself will
set these attributes post-import if necessary.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Set ``__loader__`` if set to ``None``, as if the attribute does not Set ``__loader__`` if set to ``None``, as if the attribute does not
exist. exist.
.. decorator:: set_package .. decorator:: set_package
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` to set the :attr:`__package__` attribute on the returned module. If :attr:`__package__` A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` to set the :attr:`__package__` attribute on the returned module. If :attr:`__package__`
...@@ -856,4 +860,6 @@ an :term:`importer`. ...@@ -856,4 +860,6 @@ an :term:`importer`.
.. note:: .. note::
As this decorator sets :attr:`__package__` after loading the module, it is As this decorator sets :attr:`__package__` after loading the module, it is
recommended to use :func:`module_for_loader` instead when appropriate. recommended to use :func:`module_for_loader` instead when appropriate. As
of Python 3.3 this decorator is also redundant as import will set
:attr:`__package__` post-import if necessary.
...@@ -251,3 +251,8 @@ that may require changes to your code. ...@@ -251,3 +251,8 @@ that may require changes to your code.
attributes to ``None`` by default. To determine if these attributes were set attributes to ``None`` by default. To determine if these attributes were set
in a backwards-compatible fashion, use e.g. in a backwards-compatible fashion, use e.g.
``getattr(module, '__loader__', None) is not None``. ``getattr(module, '__loader__', None) is not None``.
* :meth:`importlib.util.module_for_loader` now sets ``__loader__`` and
``__package__`` unconditionally to properly support reloading. If this is not
desired then you will need to set these attributes manually. You can use
:class:`importlib.util.ModuleManager` for module management.
...@@ -37,23 +37,13 @@ def _make_relax_case(): ...@@ -37,23 +37,13 @@ def _make_relax_case():
return _relax_case return _relax_case
# TODO: Expose from marshal
def _w_long(x): def _w_long(x):
"""Convert a 32-bit integer to little-endian. """Convert a 32-bit integer to little-endian."""
XXX Temporary until marshal's long functions are exposed.
"""
return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little') return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little')
# TODO: Expose from marshal
def _r_long(int_bytes): def _r_long(int_bytes):
"""Convert 4 bytes in little-endian to an integer. """Convert 4 bytes in little-endian to an integer."""
XXX Temporary until marshal's long function are exposed.
"""
return int.from_bytes(int_bytes, 'little') return int.from_bytes(int_bytes, 'little')
...@@ -569,17 +559,7 @@ def module_for_loader(fxn): ...@@ -569,17 +559,7 @@ def module_for_loader(fxn):
""" """
def module_for_loader_wrapper(self, fullname, *args, **kwargs): def module_for_loader_wrapper(self, fullname, *args, **kwargs):
module = sys.modules.get(fullname) with ModuleManager(fullname) as module:
is_reload = module is not None
if not is_reload:
# This must be done before open() is called as the 'io' module
# implicitly imports 'locale' and would otherwise trigger an
# infinite loop.
module = new_module(fullname)
# This must be done before putting the module in sys.modules
# (otherwise an optimization shortcut in import.c becomes wrong)
module.__initializing__ = True
sys.modules[fullname] = module
module.__loader__ = self module.__loader__ = self
try: try:
is_package = self.is_package(fullname) is_package = self.is_package(fullname)
...@@ -590,17 +570,8 @@ def module_for_loader(fxn): ...@@ -590,17 +570,8 @@ def module_for_loader(fxn):
module.__package__ = fullname module.__package__ = fullname
else: else:
module.__package__ = fullname.rpartition('.')[0] module.__package__ = fullname.rpartition('.')[0]
else:
module.__initializing__ = True
try:
# If __package__ was not set above, __import__() will do it later. # If __package__ was not set above, __import__() will do it later.
return fxn(self, module, *args, **kwargs) return fxn(self, module, *args, **kwargs)
except:
if not is_reload:
del sys.modules[fullname]
raise
finally:
module.__initializing__ = False
_wrap(module_for_loader_wrapper, fxn) _wrap(module_for_loader_wrapper, fxn)
return module_for_loader_wrapper return module_for_loader_wrapper
......
...@@ -85,12 +85,23 @@ class ModuleForLoaderTests(unittest.TestCase): ...@@ -85,12 +85,23 @@ class ModuleForLoaderTests(unittest.TestCase):
def test_reload(self): def test_reload(self):
# Test that a module is reused if already in sys.modules. # Test that a module is reused if already in sys.modules.
class FakeLoader:
def is_package(self, name):
return True
@util.module_for_loader
def load_module(self, module):
return module
name = 'a.b.c' name = 'a.b.c'
module = imp.new_module('a.b.c') module = imp.new_module('a.b.c')
module.__loader__ = 42
module.__package__ = 42
with test_util.uncache(name): with test_util.uncache(name):
sys.modules[name] = module sys.modules[name] = module
returned_module = self.return_module(name) loader = FakeLoader()
returned_module = loader.load_module(name)
self.assertIs(returned_module, sys.modules[name]) self.assertIs(returned_module, sys.modules[name])
self.assertEqual(module.__loader__, loader)
self.assertEqual(module.__package__, name)
def test_new_module_failure(self): def test_new_module_failure(self):
# Test that a module is removed from sys.modules if added but an # Test that a module is removed from sys.modules if added but an
......
...@@ -96,6 +96,9 @@ Core and Builtins ...@@ -96,6 +96,9 @@ Core and Builtins
Library Library
------- -------
- Issue #18070: Have importlib.util.module_for_loader() set attributes
unconditionally in order to properly support reloading.
- Add importlib.util.ModuleManager as a context manager to provide the proper - Add importlib.util.ModuleManager as a context manager to provide the proper
module object to load. module object to load.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment