mirror of https://github.com/python/cpython
Issue #23014: Make importlib.abc.Loader.create_module() required when
importlib.abc.Loader.exec_module() is also defined. Before this change, create_module() was optional **and** could return None to trigger default semantics. This change now reduces the options for choosing default semantics to one and in the most backporting-friendly way (define create_module() to return None).
This commit is contained in:
parent
863c69cfeb
commit
02d8454002
|
@ -347,13 +347,16 @@ ABC hierarchy::
|
|||
|
||||
.. method:: create_module(spec)
|
||||
|
||||
An optional method that returns the module object to use when
|
||||
importing a module. create_module() may also return ``None``,
|
||||
indicating that the default module creation should take place
|
||||
instead.
|
||||
A method that returns the module object to use when
|
||||
importing a module. This method may return ``None``,
|
||||
indicating that default module creation semantics should take place.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
Starting in Python 3.6, this method will not be optional when
|
||||
:meth:`exec_module` is defined.
|
||||
|
||||
.. method:: exec_module(module)
|
||||
|
||||
An abstract method that executes the module in its own namespace
|
||||
|
@ -417,7 +420,7 @@ ABC hierarchy::
|
|||
|
||||
.. deprecated:: 3.4
|
||||
The recommended API for loading a module is :meth:`exec_module`
|
||||
(and optionally :meth:`create_module`). Loaders should implement
|
||||
(and :meth:`create_module`). Loaders should implement
|
||||
it instead of load_module(). The import machinery takes care of
|
||||
all the other responsibilities of load_module() when exec_module()
|
||||
is implemented.
|
||||
|
@ -1136,9 +1139,9 @@ an :term:`importer`.
|
|||
|
||||
.. function:: module_from_spec(spec)
|
||||
|
||||
Create a new module based on **spec**.
|
||||
Create a new module based on **spec** and ``spec.loader.create_module()``.
|
||||
|
||||
If the module object is from ``spec.loader.create_module()``, then any
|
||||
If ``spec.loader.create_module()`` does not return ``None``, then any
|
||||
pre-existing attributes will not be reset. Also, no :exc:`AttributeError`
|
||||
will be raised if triggered while accessing **spec** or setting an attribute
|
||||
on the module.
|
||||
|
@ -1234,9 +1237,10 @@ an :term:`importer`.
|
|||
module has an attribute accessed.
|
||||
|
||||
This class **only** works with loaders that define
|
||||
:meth:`importlib.abc.Loader.exec_module` as control over what module type
|
||||
is used for the module is required. For the same reasons, the loader
|
||||
**cannot** define :meth:`importlib.abc.Loader.create_module`. Finally,
|
||||
:meth:`~importlib.abc.Loader.exec_module` as control over what module type
|
||||
is used for the module is required. For those same reasons, the loader's
|
||||
:meth:`~importlib.abc.Loader.create_module` method will be ignored (i.e., the
|
||||
loader's method should only return ``None``). Finally,
|
||||
modules which substitute the object placed into :attr:`sys.modules` will
|
||||
not work as there is no way to properly replace the module references
|
||||
throughout the interpreter safely; :exc:`ValueError` is raised if such a
|
||||
|
|
|
@ -339,6 +339,7 @@ of what happens during the loading portion of import::
|
|||
|
||||
module = None
|
||||
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
|
||||
# It is assumed 'exec_module' will also be defined on the loader.
|
||||
module = spec.loader.create_module(spec)
|
||||
if module is None:
|
||||
module = ModuleType(spec.name)
|
||||
|
@ -427,7 +428,7 @@ Module loaders may opt in to creating the module object during loading
|
|||
by implementing a :meth:`~importlib.abc.Loader.create_module` method.
|
||||
It takes one argument, the module spec, and returns the new module object
|
||||
to use during loading. ``create_module()`` does not need to set any attributes
|
||||
on the module object. If the loader does not define ``create_module()``, the
|
||||
on the module object. If the method returns ``None``, the
|
||||
import machinery will create the new module itself.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
@ -462,6 +463,11 @@ import machinery will create the new module itself.
|
|||
module(s), and only if the loader itself has loaded the module(s)
|
||||
explicitly.
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
A :exc:`DeprecationWarning` is raised when ``exec_module()`` is defined but
|
||||
``create_module()`` is not. Starting in Python 3.6 it will be an error to not
|
||||
define ``create_module()`` on a loader attached to a ModuleSpec.
|
||||
|
||||
Module spec
|
||||
-----------
|
||||
|
||||
|
|
|
@ -456,6 +456,14 @@ Changes in the Python API
|
|||
`http.client` and `http.server` remain available for backwards compatibility.
|
||||
(Contributed by Demian Brecht in :issue:`21793`.)
|
||||
|
||||
* When an import loader defines :meth:`~importlib.machinery.Loader.exec_module`
|
||||
it is now expected to also define
|
||||
:meth:`~importlib.machinery.Loader.create_module` (raises a
|
||||
:exc:`DeprecationWarning` now, will be an error in Python 3.6). If the loader
|
||||
inherits from :class:`importlib.abc.Loader` then there is nothing to do, else
|
||||
simply define :meth:`~importlib.machinery.Loader.create_module` to return
|
||||
``None`` (:issue:`23014`).
|
||||
|
||||
Changes in the C API
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -1055,6 +1055,10 @@ def module_from_spec(spec):
|
|||
# If create_module() returns `None` then it means default
|
||||
# module creation should be used.
|
||||
module = spec.loader.create_module(spec)
|
||||
elif hasattr(spec.loader, 'exec_module'):
|
||||
_warnings.warn('starting in Python 3.6, loaders defining exec_module() '
|
||||
'must also define create_module()',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
if module is None:
|
||||
module = _new_module(spec.name)
|
||||
_init_module_attrs(spec, module)
|
||||
|
@ -1298,6 +1302,10 @@ class FrozenImporter:
|
|||
"""
|
||||
return cls if _imp.is_frozen(fullname) else None
|
||||
|
||||
@classmethod
|
||||
def create_module(cls, spec):
|
||||
"""Use default semantics for module creation."""
|
||||
|
||||
@staticmethod
|
||||
def exec_module(module):
|
||||
name = module.__spec__.name
|
||||
|
@ -1411,6 +1419,9 @@ class _LoaderBasics:
|
|||
tail_name = fullname.rpartition('.')[2]
|
||||
return filename_base == '__init__' and tail_name != '__init__'
|
||||
|
||||
def create_module(self, spec):
|
||||
"""Use default semantics for module creation."""
|
||||
|
||||
def exec_module(self, module):
|
||||
"""Execute the module."""
|
||||
code = self.get_code(module.__name__)
|
||||
|
@ -1771,6 +1782,9 @@ class _NamespaceLoader:
|
|||
def get_code(self, fullname):
|
||||
return compile('', '<string>', 'exec', dont_inherit=True)
|
||||
|
||||
def create_module(self, spec):
|
||||
"""Use default semantics for module creation."""
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
|
|
|
@ -122,9 +122,6 @@ class Loader(metaclass=abc.ABCMeta):
|
|||
This method should raise ImportError if anything prevents it
|
||||
from creating a new module. It may return None to indicate
|
||||
that the spec should create the new module.
|
||||
|
||||
create_module() is optional.
|
||||
|
||||
"""
|
||||
# By default, defer to default semantics for the new module.
|
||||
return None
|
||||
|
|
|
@ -11,6 +11,9 @@ class SpecLoaderMock:
|
|||
def find_spec(self, fullname, path=None, target=None):
|
||||
return machinery.ModuleSpec(fullname, self)
|
||||
|
||||
def create_module(self, spec):
|
||||
return None
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
|
|
|
@ -16,6 +16,10 @@ class BadSpecFinderLoader:
|
|||
spec = machinery.ModuleSpec(fullname, cls)
|
||||
return spec
|
||||
|
||||
@staticmethod
|
||||
def create_module(spec):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def exec_module(module):
|
||||
if module.__name__ == SUBMOD_NAME:
|
||||
|
|
|
@ -34,6 +34,9 @@ class TestLoader:
|
|||
def _is_package(self, name):
|
||||
return self.package
|
||||
|
||||
def create_module(self, spec):
|
||||
return None
|
||||
|
||||
|
||||
class NewLoader(TestLoader):
|
||||
|
||||
|
|
|
@ -41,10 +41,16 @@ class DecodeSourceBytesTests:
|
|||
class ModuleFromSpecTests:
|
||||
|
||||
def test_no_create_module(self):
|
||||
class Loader(self.abc.Loader):
|
||||
pass
|
||||
class Loader:
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
spec = self.machinery.ModuleSpec('test', Loader())
|
||||
module = self.util.module_from_spec(spec)
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter('always')
|
||||
module = self.util.module_from_spec(spec)
|
||||
self.assertEqual(1, len(w))
|
||||
self.assertTrue(issubclass(w[0].category, DeprecationWarning))
|
||||
self.assertIn('create_module', str(w[0].message))
|
||||
self.assertIsInstance(module, types.ModuleType)
|
||||
self.assertEqual(module.__name__, spec.name)
|
||||
|
||||
|
|
|
@ -104,6 +104,9 @@ class PkgutilTests(unittest.TestCase):
|
|||
class PkgutilPEP302Tests(unittest.TestCase):
|
||||
|
||||
class MyTestLoader(object):
|
||||
def create_module(self, spec):
|
||||
return None
|
||||
|
||||
def exec_module(self, mod):
|
||||
# Count how many times the module is reloaded
|
||||
mod.__dict__['loads'] = mod.__dict__.get('loads', 0) + 1
|
||||
|
|
4705
Python/importlib.h
4705
Python/importlib.h
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue