[Python-Dev] PEP 420 - dynamic path computation is missing rationale

Eric V. Smith eric at trueblade.com
Tue May 22 16:51:22 CEST 2012


On 05/21/2012 07:25 PM, Nick Coghlan wrote:
> As a simple example to back up PJE's explanation, consider:
> 1.  encodings becomes a namespace package
> 2. It sometimes gets imported during interpreter startup to initialise
> the standard io streams
> 3. An application modifies sys.path after startup and wants to
> contribute additional encodings
> 
> Searching the entire parent path for new portions on every import would
> be needlessly slow.
> 
> Not recognising new portions would be needlessly confusing for users. In
> our simple case above, the application would fail if the io
> initialisation accessed the encodings package, but work if it did not
> (e.g. when all streams are utf-8).
> 
> PEP 420 splits the difference via an automatically invalidated cache:
> when you iterate over a namespace package __path__ object, it rescans
> the parent path for new portions *if and only if* the contents of the
> parent path have changed since the previous scan.

That seems like a pretty convincing example to me.

Personally I'm +1 on putting dynamic computation into the PEP, at least
for top-level namespace packages, and probably for all namespace packages.

The code is not very large or complicated, and with the proposed removal
of the restriction that sys.path cannot be replaced, I think it behaves
well.

But Guido can decide against it without hurting my feelings.

Eric.

P.S.: Here's the current code in the pep-420 branch. This code still has
the restriction that sys.path (or parent_path in general) can't be
replaced. I'll fix that if we decide to keep the feature.

class _NamespacePath:
    def __init__(self, name, path, parent_path, path_finder):
        self._name = name
        self._path = path
        self._parent_path = parent_path
        self._last_parent_path = tuple(parent_path)
        self._path_finder = path_finder

    def _recalculate(self):
        # If _parent_path has changed, recalculate _path
        parent_path = tuple(self._parent_path)     # Make a copy
        if parent_path != self._last_parent_path:
            loader, new_path = self._path_finder(self._name, parent_path)
            # Note that no changes are made if a loader is returned, but we
            #  do remember the new parent path
            if loader is None:
                self._path = new_path
            self._last_parent_path = parent_path   # Save the copy
        return self._path

    def __iter__(self):
        return iter(self._recalculate())

    def __len__(self):
        return len(self._recalculate())

    def __repr__(self):
        return "_NamespacePath" + repr((self._path, self._parent_path))

    def __contains__(self, item):
        return item in self._recalculate()





More information about the Python-Dev mailing list