Re: PEP 451: Big update.
Brett Cannon <[email protected]> Wed, 18 Sep 2013 10:57:44 -0400
| Newsgroups | gmane.comp.python.import |
|---|---|
| Message-ID | <CAP1=2W6Ax1Y63d5jPUT=kYeg3=f7TUW+zyYn0ZrpfbdfcnE0tQ@mail.gmail.com> |
Looking good! Comments inline. On Wed, Sep 18, 2013 at 5:51 AM, Eric Snow <[email protected]>wrote: > Hi all, > > I finally got some time to update the PEP. I've simplified a few things, > most notably by making the 4 ModuleSpec methods (create, exec, load, > reload) "private". > > Also notable is that the new loader method is still create_module() and > there is still no flag for is_reload on either of the loader methods. I'm > still not clear on what the flag buys us and on why anything we'd do in a > prepare_module() we couldn't do in exec_module(). I'm trying to keep this > simple. :) > > Anyway, I still need to take some time to clean up the PEP formatting and > run a spell checker. I probably also missed some artifact of an older > version of the API. Otherwise I think it's in a good spot. Comments > welcome. > > -eric > > p.s. I also plan on getting the implementation up one of these days. :P > > =============================================================== > > PEP: 451 > Title: A ModuleSpec Type for the Import System > Version: $Revision$ > Last-Modified: $Date$ > Author: Eric Snow <[email protected]> > Discussions-To: [email protected] > Status: Draft > Type: Standards Track > Content-Type: text/x-rst > Created: 8-Aug-2013 > Python-Version: 3.4 > Post-History: 8-Aug-2013, 28-Aug-2013, 18-Sep-2013 > Resolution: > > [SNIP] > Specification > ============= > > The goal is to address the gap between finders and loaders while > changing as little of their semantics as possible. Though some > functionality and information is moved to the new ``ModuleSpec`` type, > their behavior should remain the same. However, for the sake of clarity > the finder and loader semantics will be explicitly identified. > > This is a high-level summary of the changes described by this PEP. More > detail is available in later sections. > > importlib.machinery.ModuleSpec (new) > ------------------------------------ > > A specification for a module's import-system-related state. > > * ModuleSpec(name, loader, \*, origin=None, loading_info=None, > is_package=None) > > Attributes: > > * name - a string for the name of the module. > * loader - the loader to use for loading and for module data. > Just drop the "and for module data"; sentence is awkward with it and is a margin use-case. > * origin - a string for the location from which the module is loaded, > e.g. "builtin" for built-in modules and the filename for modules > loaded from source. > * submodule_search_locations - strings for where to find submodules, > if a package. > Very subtle hint that it's a sequence of of strings; might want to make it more explicit that it's a list. > * loading_info - a container of extra data for use during loading. > * cached (property) - a string for where the compiled module will be > stored (see PEP 3147). > * package (RO-property) - the name of the module's parent (or None). > * has_location (RO-property) - the module's origin refers to a location. > > Instance Methods: > > * module_repr() - provide a repr string for the spec'ed module. > * init_module_attrs(module) - set any of a module's import-related > attributes that aren't already set. > > importlib.util Additions > ------------------------ > > * spec_from_file_location(name, location, \*, loader=None, > submodule_search_locations=None) > - factory for file-based module specs. > * from_loader(name, loader, \*, origin=None, is_package=None) - factory > based on information provided by loaders. > * spec_from_module(module, loader=None) - factory based on existing > import-related module attributes. This function is expected to be > used only in some backward-compatibility situations. > > Other API Additions > ------------------- > > * importlib.abc.Loader.exec_module(module) will execute a module in its > own namespace. It replaces ``importlib.abc.Loader.load_module()``. > * importlib.abc.Loader.create_module(spec) (optional) will return a new > module to use for loading. > * Module objects will have a new attribute: ``__spec__``. > * importlib.find_spec(name, path=None) will return the spec for a > module. > > exec_module() and create_module() should not set any import-related > module attributes. The fact that load_module() does is a design flaw > that this proposal aims to correct. > This is a rather jarring place to make this statement since you're just outlining API additions, not design decisions. > > API Changes > ----------- > > * ``InspectLoader.is_package()`` will become optional. > > Deprecations > ------------ > > * importlib.abc.MetaPathFinder.find_module() > * importlib.abc.PathEntryFinder.find_module() > * importlib.abc.PathEntryFinder.find_loader() > * importlib.abc.Loader.load_module() > * importlib.abc.Loader.module_repr() > * The parameters and attributes of the various loaders in > importlib.machinery > * importlib.util.set_package() > * importlib.util.set_loader() > * importlib.find_loader() > Yay to all of this! =) > > Removals > -------- > > These were introduced prior to Python 3.4's release. > > * importlib.abc.Loader.init_module_attrs() > * importlib.util.module_to_load() > > Other Changes > ------------- > > * The import system implementation in importlib will be changed to make > use of ModuleSpec. > * Import-related module attributes (other than ``__spec__``) will no > longer be used directly by the import system. > * Import-related attributes should no longer be added to modules > directly. > * The module type's ``__repr__()`` will be thin wrapper around a pure > Python implementation which will leverage ModuleSpec. > "be a thin" > * The spec for the ``__main__`` module will reflect the appropriate > name and origin. > > Backward-Compatibility > ---------------------- > > * If a finder does not define find_spec(), a spec is derived from > the loader returned by find_module(). > * PathEntryFinder.find_loader() still takes priority over > find_module(). > * Loader.load_module() is used if exec_module() is not defined. > > What Will not Change? > --------------------- > > * The syntax and semantics of the import statement. > * Existing finders and loaders will continue to work normally. > * The import-related module attributes will still be initialized with > the same information. > * Finders will still create loaders (now storing them in specs). > * Loader.load_module(), if a module defines it, will have all the > same requirements and may still be called directly. > * Loaders will still be responsible for module data APIs. > * importlib.reload() will still overwrite the import-related attributes. > > > What Will Existing Finders and Loaders Have to Do Differently? > ============================================================== > > Immediately? Nothing. The status quo will be deprecated, but will > continue working. However, here are the things that the authors of > finders and loaders should change relative to this PEP: > > * Implement ``find_spec()`` on finders. > * Implement ``exec_module()`` on loaders, if possible. > > The ModuleSpec factory functions in importlib.util are intended to be > helpful for converting existing finders. ``from_loader()`` and > ``from_file_location()`` are both straight-forward utilities in this > regard. In the case where loaders already expose methods for creating > and preparing modules, ``ModuleSpec.from_module()`` may be useful to > the corresponding finder. > > For existing loaders, exec_module() should be a relatively direct > conversion from the non-boilerplate portion of load_module(). In some > uncommon cases the loader should also implement create_module(). > > > ModuleSpec Users > ================ > > ``ModuleSpec`` objects has 3 distinct target audiences: Python itself, > import hooks, and normal Python users. > "has" -> "have" > > Python will use specs in the import machinery, in interpreter startup, > and in various standard library modules. Some modules are > import-oriented, like pkgutil, and others are not, like pickle and > pydoc. In all cases, the full ``ModuleSpec`` API will get used. > > Import hooks (finders and loaders) will make use of the spec in specific > ways. First of all, finders may use the spec factory functions in > importlib.util to create spec objects. They may also directly adjust > the spec attributes after the spec is created. Secondly, the finder may > bind additional information to the spec (in finder_extras) for the > loader to consume during module creation/execution. Finally, loaders > will make use of the attributes on a spec when creating and/or executing > a module. > > Python users will be able to inspect a module's ``__spec__`` to get > import-related information about the object. Generally, Python > applications and interactive users will not be using the ``ModuleSpec`` > factory functions nor any the instance methods. > > > How Loading Will Work > ===================== > > This is an outline of what happens in ModuleSpec's loading > functionality:: > > def load(spec): > if not hasattr(spec.loader, 'exec_module'): > module = spec.loader.load_module(spec.name) > spec.init_module_attrs(module) > return sys.modules[spec.name] > > module = None > if hasattr(spec.loader, 'create_module'): > module = spec.loader.create_module(spec) > if module is None: > module = ModuleType(spec.name) > spec.init_module_attrs(module) > > spec._initializing = True > sys.modues[spec.name] = module > try: > spec.loader.exec_module(module) > except Exception: > del sys.modules[spec.name] > finally: > spec._initializing = False > return sys.modules[spec.name] > > These steps are exactly what ``Loader.load_module()`` is already > expected to do. Loaders will thus be simplified since they will only > need to implement exec_module(). > Two things. One, it's not exactly what loaders do as that _initializing is done by import itself. Any specific reason you added it here? Two, you forgot to re-raise the exception in the except clause. > > Note that we must return the module from sys.modules. During loading > the module may have replaced itself in sys.modules. Since we don't have > a post-import hook API to accommodate the use case, we have to deal with > it. However, in the replacement case we do not worry about setting the > import-related module attributes on the object. The module writer is on > their own if they are doing this. > > > ModuleSpec > ========== > > Attributes > ---------- > > Each of the following names is an attribute on ModuleSpec objects. A > value of ``None`` indicates "not set". This contrasts with module > objects where the attribute simply doesn't exist. Most of the > attributes correspond to the import-related attributes of modules. Here > is the mapping. The reverse of this mapping is used by > ModuleSpec.init_module_attrs(). > > ========================== ============== > On ModuleSpec On Modules > ========================== ============== > name __name__ > loader __loader__ > package __package__ > origin __file__* > cached __cached__*,** > submodule_search_locations __path__** > loading_info \- > has_location \- > ========================== ============== > > \* Set only if has_location is true. > \*\* Set only if the spec attribute is not None. > "Set on the module if the spec" > > While package and has_location are read-only properties, the remaining > attributes can be replaced after the module spec is created and even > after import is complete. This allows for unusual cases where directly > modifying the spec is the best option. However, typical use should not > involve changing the state of a module's spec. > > **origin** > > origin is a string for the place from which the module originates. > Aside from the informational value, it is also used in module_repr(). > > The module attribute ``__file__`` has a similar but more restricted > meaning. Not all modules have it set (e.g. built-in modules). However, > ``origin`` is applicable to all modules. For built-in modules it would > be set to "built-in". > > **has_location** > > Some modules can be loaded by reference to a location, e.g. a filesystem > path or a URL or something of the sort. Having the location lets you > load the module, but in theory you could load that module under various > names. > > In contrast, non-located modules can't be loaded in this fashion, e.g. > builtin modules and modules dynamically created in code. For these, the > name is the only way to access them, so they have an "origin" but not a > "location". > > This attribute reflects whether or not the module is locatable. If it > is, origin must be set to the module's location and ``__file__`` will be > set on the module. Not all locatable modules will be cachable, but most > will. > > The corresponding module attribute name, ``__file__``, is somewhat > inaccurate and potentially confusion, > "confusion" -> "confusing" > so we will use a more explicit > combination of origin and has_location to represent the same > information. Having a separate filename is unncessary since we have > origin. > Quote 'origin' so you don't read it like it should have been written "we have an origin". > > **submodule_search_locations** > > The list of location strings, typically directory paths, in which to > search for submodules. If the module is a package this will be set to > a list (even an empty one). Otherwise it is ``None``. > > The corresponding module attribute's name, ``__path__``, is relatively > ambiguous. Instead of mirroring it, we use a more explicit name that > makes the purpose clear. > > **loading_info** > > A finder may set loading_info to any value to provide additional > data for the loader to use during loading. A value of None is the > default and indicates that there is no additional data. Otherwise it > can be set to any object, such as a dict, list, or > types.SimpleNamespace, containing the relevant extra information. > > For example, zipimporter could use it to pass the zip archive name > to the loader directly, rather than needing to derive it from origin > or create a custom loader for each find operation. > > loading_info is meant for use by the finder and corresponding loader. > It is not guaranteed to be a stable resource for any other use. > > Omitted Attributes and Methods > ------------------------------ > > The following ModuleSpec methods are not part of the public API since > it is easy to use them incorrectly and only the import system really > needs them (i.e. they would be an attractive nuisance). > > * create() - provide a new module to use for loading. > * exec(module) - execute the spec into a module namespace. > * load() - prepare a module and execute it in a protected way. > * reload(module) - re-execute a module in a protected way. > If they are not part of the public API they should have a leading underscore. > > Here are other omissions: > > There is no PathModuleSpec subclass of ModuleSpec that separates out > has_location, cached, and submodule_search_locations. While that might > make the separation cleaner, module objects don't have that distinction. > ModuleSpec will support both cases equally well. > > While is_package would be a simple additional attribute (aliasing > ``self.submodule_search_locations is not None``), it perpetuates the > artificial (and mostly erroneous) distinction between modules and > packages. > > Conceivably, a ModuleSpec.load() method could optionally take a list of > modules with which to interact instead of sys.modules. That > capability is left out of this PEP, but may be pursued separately at > some other time, including relative to PEP 406 (import engine). > > Likewise load() could be leveraged to implement multi-version > imports. While interesting, doing so is outside the scope of this > proposal. > > Others: > > * Add ModuleSpec.submodules (RO-property) - returns possible submodules > relative to the spec. > * Add ModuleSpec.loaded (RO-property) - the module in sys.module, if > any. > * Add ModuleSpec.data - a descriptor that wraps the data API of the > spec's loader. > * Also see [3]. > > > Backward Compatibility > ---------------------- > > ModuleSpec doesn't have any. This would be a different story if > Finder.find_module() were to return a module spec instead of loader. > In that case, specs would have to act like the loader that would have > been returned instead. Doing so would be relatively simple, but is an > unnecessary complication. It was part of earlier versions of this PEP. > > Subclassing > ----------- > > Subclasses of ModuleSpec are allowed, but should not be necessary. > Simply setting loading_info or adding functionality to a custom > finder or loader will likely be a better fit and should be tried first. > However, as long as a subclass still fulfills the requirements of the > import system, objects of that type are completely fine as the return > value of Finder.find_spec(). > > > > [SNIP] > > > Open Issues > ============== > > \* The impact of this change on pkgutil (and setuptools) needs looking > into. It has some generic function-based extensions to PEP 302. These > may break if importlib starts wrapping loaders without the tools' > knowledge. > > \* Other modules to look at: runpy (and pythonrun.c), pickle, pydoc, > inspect. > > For instance, pickle should be updated in the __main__ case to look at > ``module.__spec__.name``. > > \* Impact on some kinds of lazy loading modules. See [3]. > > \* Find a better name than loading_info? Perhaps loading_data, > loader_state, or loader_info. > loader_state or loader_data get my vote. > > \* Change loader.create_module() to prepare_module()? > -0 from me. -Brett _______________________________________________ Import-SIG mailing list [email protected] https://mail.python.org/mailman/listinfo/import-sig