mirror of
https://github.com/python/cpython.git
synced 2025-07-16 15:55:18 +00:00
Issue #18070: importlib.util.module_for_loader() now sets __loader__
and __package__ unconditionally in order to do the right thing for reloading.
This commit is contained in:
parent
a22faca714
commit
3dc48d6f69
6 changed files with 3473 additions and 3496 deletions
|
@ -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`.
|
||||||
: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`.
|
||||||
.. 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`.
|
||||||
|
|
||||||
.. 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.
|
||||||
|
|
|
@ -250,4 +250,9 @@ that may require changes to your code.
|
||||||
* The module type now initializes the :attr:`__package__` and :attr:`__loader__`
|
* The module type now initializes the :attr:`__package__` and :attr:`__loader__`
|
||||||
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():
|
||||||
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):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
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
|
||||||
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.
|
||||||
|
|
||||||
|
|
6889
Python/importlib.h
6889
Python/importlib.h
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue