Skip to content
8 changes: 4 additions & 4 deletions Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ ABC hierarchy::

.. abstractmethod:: contents()

Returns an :term:`iterator` of strings over the contents of
Returns an :term:`iterable` of strings over the contents of
the package. Do note that it is not required that all names
returned by the iterator be actual resources, e.g. it is
acceptable to return names for which :meth:`is_resource` would
Expand All @@ -544,7 +544,7 @@ ABC hierarchy::
the file system then those subdirectory names can be used
directly.

The abstract method returns an iterator of no items.
The abstract method returns an iterable of no items.


.. class:: ResourceLoader
Expand Down Expand Up @@ -926,9 +926,9 @@ The following functions are available.

.. function:: contents(package)

Return an iterator over the named items within the package. The iterator
Return an iterable over the named items within the package. The iterable
returns :class:`str` resources (e.g. files) and non-resources
(e.g. directories). The iterator does not recurse into subdirectories.
(e.g. directories). The iterable does not recurse into subdirectories.

*package* is either a name or a module object which conforms to the
``Package`` requirements.
Expand Down
4 changes: 2 additions & 2 deletions Lib/importlib/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,8 @@ def is_resource(self, name):

@abc.abstractmethod
def contents(self):
"""Return an iterator of strings over the contents of the package."""
return iter([])
"""Return an iterable of strings over the contents of the package."""
return []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe return a tuple? Creating and iterating a tuple is a tiny bit faster, and the empty tuple consumes less memory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+0 - I leave it up to @brettcannon



_register(ResourceReader, machinery.SourceFileLoader)
21 changes: 10 additions & 11 deletions Lib/importlib/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from io import BytesIO, TextIOWrapper
from pathlib import Path
from types import ModuleType
from typing import Iterator, Optional, Set, Union # noqa: F401
from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
from typing import cast
from typing.io import BinaryIO, TextIO
from zipimport import ZipImportError
Expand Down Expand Up @@ -44,8 +44,7 @@ def _normalize_path(path) -> str:

If the resulting string contains path separators, an exception is raised.
"""
str_path = str(path)
parent, file_name = os.path.split(str_path)
parent, file_name = os.path.split(path)
if parent:
raise ValueError('{!r} must be only a file name'.format(path))
else:
Expand Down Expand Up @@ -228,8 +227,8 @@ def is_resource(package: Package, name: str) -> bool:
return path.is_file()


def contents(package: Package) -> Iterator[str]:
"""Return the list of entries in 'package'.
def contents(package: Package) -> Iterable[str]:
"""Return an iterable of entries in 'package'.

Note that not all entries are resources. Specifically, directories are
not considered resources. Use `is_resource()` on each entry returned here
Expand All @@ -238,15 +237,15 @@ def contents(package: Package) -> Iterator[str]:
package = _get_package(package)
reader = _get_resource_reader(package)
if reader is not None:
yield from reader.contents()
return
return reader.contents()
# Is the package a namespace package? By definition, namespace packages
# cannot have resources. We could use _check_location() and catch the
# exception, but that's extra work, so just inline the check.
if package.__spec__.origin is None or not package.__spec__.has_location:
return []
package_directory = Path(package.__spec__.origin).parent
yield from os.listdir(str(package_directory))
elif package.__spec__.origin is None or not package.__spec__.has_location:
return ()
else:
package_directory = Path(package.__spec__.origin).parent
return os.listdir(package_directory)


# Private implementation of ResourceReader and get_resource_reader() for
Expand Down
7 changes: 3 additions & 4 deletions Lib/test/test_importlib/test_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,9 @@ def test_submodule_contents_by_name(self):


class NamespaceTest(unittest.TestCase):
def test_namespaces_cant_have_resources(self):
contents = set(resources.contents(
'test.test_importlib.data03.namespace'))
self.assertEqual(len(contents), 0)
def test_namespaces_cannot_have_resources(self):
contents = resources.contents('test.test_importlib.data03.namespace')
self.assertFalse(list(contents))
# Even though there is a file in the namespace directory, it is not
# considered a resource, since namespace packages can't have them.
self.assertFalse(resources.is_resource(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Have :func:`importlib.resources.contents` and
:meth:`importlib.abc.ResourceReader.contents` return an :term:`iterable` instead
of an :term:`iterator`.