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 diff is collapsed.
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