I like having the shared Traversable value as the module level PATH constant. Passing that around and calling joinpath on it makes it fun to use. If I would create my own wrapper it would implement Traversable and look something like the following:
import os
import pathlib
from collections.abc import Iterator
from importlib.resources import files
from importlib.resources.abc import Traversable
from types import ModuleType
class LazyTraversable(Traversable):
"""
A lazy traversable.
Use LazyTraversable.make(__name__) to create.
"""
__slots__ = ("_anchor", "_path_segments")
_anchor: str | ModuleType
_path_segments: tuple[str | os.PathLike[str], ...]
def __init__(
self, anchor: str | ModuleType, *segments: str | os.PathLike[str]
) -> None:
"""Initialize self."""
self._anchor = anchor
self._path_segments = segments
@classmethod
def make(cls, anchor: str | ModuleType) -> Traversable:
"""
Create a new Traversable.
Multiprocesing-safe alternative to `importlib.resources.files`.
"""
if isinstance(traversable := files(anchor), pathlib.Path):
return traversable
return cls(anchor)
def _traversable(self) -> Traversable:
return files(self._anchor).joinpath(*self._path_segments)
def iterdir(self) -> Iterator[Traversable]:
"""Yield Traversable objects in self."""
return self._traversable().iterdir()
def is_dir(self) -> bool:
"""Return True if self is a directory."""
return self._traversable().is_dir()
def is_file(self) -> bool:
"""Return True if self is a file."""
return self._traversable().is_file()
def joinpath(self, *descendants: str | os.PathLike[str]) -> Traversable:
"""
Return Traversable resolved with any descendants applied.
Each descendant should be a path segment relative to self
and each may contain multiple levels separated by
``posixpath.sep`` (``/``).
"""
return LazyTraversable(self._anchor, *self._path_segments, *descendants)
def open(self, mode="r", *args, **kwargs):
"""
Open the file.
mode may be 'r' or 'rb' to open as text or binary. Return a handle
suitable for reading (same as pathlib.Path.open).
When opening as text, accepts encoding parameters such as those
accepted by io.TextIOWrapper.
"""
return self._traversable().open(mode, *args, **kwargs)
@property
def name(self) -> str:
"""The base name of this object without any parent references."""
return self._traversable().name
PATH = LazyTraversable.make(__name__)
Your benchmark is flawed as it’s just creating a pathlib.Path and not opening a ZipFile. So I think this wrapper could have a negative performance impact when running in a single-process zipapp.