135

Is there a straightforward way to list the names of all modules in a package, without using __all__?

For example, given this package:

/testpkg
/testpkg/__init__.py
/testpkg/modulea.py
/testpkg/moduleb.py

I'm wondering if there is a standard or built-in way to do something like this:

>>> package_contents("testpkg")
['modulea', 'moduleb']

The manual approach would be to iterate through the module search paths in order to find the package's directory. One could then list all the files in that directory, filter out the uniquely-named py/pyc/pyo files, strip the extensions, and return that list. But this seems like a fair amount of work for something the module import mechanism is already doing internally. Is that functionality exposed anywhere?

DNS
  • 37,249
  • 18
  • 95
  • 132

12 Answers12

237

Using python2.3 and above, you could also use the pkgutil module:

>>> import pkgutil
>>> [name for _, name, _ in pkgutil.iter_modules(['testpkg'])]
['modulea', 'moduleb']

EDIT: Note that the parameter for pkgutil.iter_modules is not a list of modules, but a list of paths, so you might want to do something like this:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print([name for _, name, _ in pkgutil.iter_modules([pkgpath])])
Intrastellar Explorer
  • 3,005
  • 9
  • 52
  • 119
jp.
  • 2,399
  • 2
  • 13
  • 3
  • 20
    This is disturbingly undocumented, but seems like the most correct way to do this. Hope you don't mind I added the note. – itsadok Nov 24 '09 at 15:01
  • 17
    `pkgutil` is there in [python2.3 and up actually](http://docs.python.org/library/pkgutil.html). Also, while `pkgutil.iter_modules()` will not work recursively, there is a `pkgutil.walk_packages()` as well, which _will_ recurse. Thanks for the pointer to this package though. – Sandip Bhattacharya Jan 29 '12 at 19:04
  • Why `iter_modules` doesn't work for absolute import like `a.b.testpkg`? It is giving me `[]` – Hussain Feb 25 '17 at 13:52
  • Taking the OP literally, I don't see why you would need to use `os.path.dirname`, but then again, I think it will work with or without it because testpkg is a directory. – AlanSE Oct 24 '17 at 18:47
  • 4
    I can't confirm that `pkgutil.walk_packages()` recurses, it gives me the same output as `pkgutil.iter_modules()`, so I think the answer is incomplete. – rwst Jan 21 '19 at 16:07
  • This method fails to find `os.path` as a submodule of `os`. But maybe this is a special case? – jochen May 25 '19 at 17:32
  • 2
    If anyone cares, this also works perfectly in python 3.8 (Just saying as original post is quiet old) – Ruben Bob Feb 05 '20 at 11:07
  • 1
    I don't think you need to use os at all here. You should be able to just do: `[name for _, name, _ in pkgutil.iter_modules(testpkg.__path__)]` – kbuilds Aug 26 '20 at 19:10
  • @kbuilds @AlanSE, you need `dirname` because `pkg.__file__` points to `__init__.py` file – Eugene K Oct 26 '20 at 10:31
  • what if my package name is `Office365-REST-Python-Client` – sjd Apr 27 '22 at 12:32
  • @sjd That's impossible, because Python package names can't have dashes in them. (**Distribution** package names at e.g. pypi.org can use dashes in the name, but when that's installed the package name is something else.) For your example, `pip install Office365-REST-Python-Client` installs a python package named `office365`, with subpackages like `office365.sharepoint` and `office365.runtime`. (Possibly others, I'm just going by their README's example code.) – FeRD Mar 02 '23 at 16:10
48
import module
help(module)
Kenan Banks
  • 207,056
  • 34
  • 155
  • 173
  • 3
    Although help does list package contents at the bottom of the help text, the question is more along the lines of how to do this: f(package_name) => ["module1_name", "module2_name"]. I suppose I could parse the string returned by help, but that seems more roundabout than listing the directory. – DNS Jan 28 '09 at 21:56
  • 1
    @DNS: `help()` prints stuff, it doesn't return a string. – Junuxx Jan 15 '13 at 14:45
  • 1
    I agree this is a roundabout way but it sent me down a rabbit hole to see how `help()` works. Anyway, the built-in `pydoc` module can help spit out the string that `help()` paginates: `import pydoc; pydoc.render_doc('mypackage')`. – sraboy May 16 '19 at 13:00
24

Maybe this will do what you're looking for?

import imp
import os
MODULE_EXTENSIONS = ('.py', '.pyc', '.pyo')

def package_contents(package_name):
    file, pathname, description = imp.find_module(package_name)
    if file:
        raise ImportError('Not a package: %r', package_name)
    # Use a set because some may be both source and compiled.
    return set([os.path.splitext(module)[0]
        for module in os.listdir(pathname)
        if module.endswith(MODULE_EXTENSIONS)])
James Wiseman
  • 29,946
  • 17
  • 95
  • 158
cdleary
  • 69,512
  • 53
  • 163
  • 191
  • 1
    I would add 'and module != "__init__.py"' to the final 'if', since __init__.py isn't really part of the package. And .pyo is another valid extension. Aside from that, using imp.find_module is a really good idea; I think this is the right answer. – DNS Jan 28 '09 at 22:39
  • 3
    I disagree -- you can import __init__ directly, so why special case it? It sure isn't special enough to break the rules. ;-) – cdleary Jan 28 '09 at 22:51
  • 6
    You should probably use `imp.get_suffixes()` instead of your hand-written list. – itsadok Nov 24 '09 at 14:29
  • 3
    Also, note that this doesn't work on subpackages like `xml.sax` – itsadok Nov 24 '09 at 14:47
  • 1
    What is the purpose of `[os.path.splitext(module)[0]`? First of all, module is not defined... do you mean 'package_name' or 'pathname' (which near as I can tell, are always the same), and then why strip the extension from a package name? Would there ever be one? And for that matter, why include the package at all? – Eric Haynes Sep 30 '16 at 02:10
  • @erich2k8 1) module is defined in the list comprehension, 2) they are stripping the extension from the module name because this method returns the names of modules inside the given package – Terrence Jul 19 '17 at 18:39
  • 1
    This is a really bad way. You can't reliably tell what's a module from the filename extension. – wim Jan 24 '18 at 18:19
  • Wouldn't work in Jython too if the Jython module is a bunch of compiled `.py$class` files in a .jar file – Jason S Aug 16 '18 at 20:20
  • 1
    This technique will only work for simple packages structured on the filesystem. It won't work for eggs, zip wheels, or packages where modules are loaded dynamically. – Jason R. Coombs Feb 10 '19 at 16:29
18

Don't know if I'm overlooking something, or if the answers are just out-dated but;

As stated by user815423426 this only works for live objects and the listed modules are only modules that were imported before.

Listing modules in a package seems really easy using inspect:

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
  • 18,867
  • 14
  • 64
  • 107
  • I've put imported = __import__('myproj.mymod.mysubmod') m = inspect.getmembers(i, inspect.ismodule) but importd path is ~/myproj/__init__.py and m is a list with (mymod, '~/myproj/mymod/__init__.py') – hithwen Apr 15 '13 at 12:09
  • 1
    @hithwen Don't ask questions in the comments, especially if they're not directly related. Being a good Samaritan: Use `imported = import importlib; importlib.import_module('myproj.mymod.mysubmod')`. `__import__` imports the top-level module, [see the documentation](http://docs.python.org/2/library/functions.html#__import__). – siebz0r Apr 15 '13 at 12:40
  • Hmm, this is promising but it's not working for me. When I do `import inspect, mypackage` and then `inspect.getmembers(my_package, inspect.ismodule)` I get an empty list, even though I certainly have various modules in it. – Amelio Vazquez-Reina Jun 26 '13 at 16:48
  • 1
    As a matter of fact, this only seems to work if I `import my_package.foo` and not just `import mypackage`, in which case it then returns `foo`. But this defeats the purpose – Amelio Vazquez-Reina Jun 26 '13 at 16:52
  • By the way, if you read the documentation of `inspect`, it explicitly says it is a library to inspect **live objects**, which is why it doesn't show anything that has not been loaded yet. – Amelio Vazquez-Reina Jun 26 '13 at 16:54
  • 3
    @user815423426 You are absolutely right ;-) Seems like I was overlooking something. – siebz0r Jun 26 '13 at 20:01
4

This is a recursive version that works with python 3.6 and above:

import importlib.util
from pathlib import Path
import os
MODULE_EXTENSIONS = '.py'

def package_contents(package_name):
    spec = importlib.util.find_spec(package_name)
    if spec is None:
        return set()

    pathname = Path(spec.origin).parent
    ret = set()
    with os.scandir(pathname) as entries:
        for entry in entries:
            if entry.name.startswith('__'):
                continue
            current = '.'.join((package_name, entry.name.partition('.')[0]))
            if entry.is_file():
                if entry.name.endswith(MODULE_EXTENSIONS):
                    ret.add(current)
            elif entry.is_dir():
                ret.add(current)
                ret |= package_contents(current)


    return ret
tacaswell
  • 84,579
  • 22
  • 210
  • 199
  • What's the advantage of using `os.scandir` as a context manager instead of iterating over the result entries directly? – monkut May 30 '18 at 15:00
  • 1
    @monkut See https://docs.python.org/3/library/os.html#os.scandir which suggest using it as a context manager to ensure that `close` is called when you are done with it to ensure that any held resources are released. – tacaswell Jun 04 '18 at 03:06
  • this doesnt work for `re` instead it lists every package but adds `re.` to all of them – tushortz Jan 17 '19 at 11:25
3

This should list the modules:

help("modules")
Ammon
  • 121
  • 1
  • 3
  • 4
    This is not an answer. It prints all available modules, while author asks for modules in a given package. – belkka Nov 25 '21 at 16:51
3

There is a __loader__ variable inside each package instance. So, if you import the package, you can find the "module resources" inside the package:

import testpkg # change this by your package name

for mod in testpkg.__loader__.get_resource_reader().contents():
    print(mod)

You can of course improve the loop to find the "module" name:

import testpkg

from pathlib import Path

for mod in testpkg.__loader__.get_resource_reader().contents():
    # You can filter the name like 
    # Path(l).suffix not in (".py", ".pyc")
    print(Path(mod).stem)
    

Inside the package, you can find your modules by directly using __loader__ of course.

Metal3d
  • 2,905
  • 1
  • 23
  • 29
2

If you would like to view an inforamtion about your package outside of the python code (from a command prompt) you can use pydoc for it.

# get a full list of packages that you have installed on you machine
$ python -m pydoc modules

# get information about a specific package
$ python -m pydoc <your package>

You will have the same result as pydoc but inside of interpreter using help

>>> import <my package>
>>> help(<my package>)
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
1

Based on cdleary's example, here's a recursive version listing path for all submodules:

import imp, os

def iter_submodules(package):
    file, pathname, description = imp.find_module(package)
    for dirpath, _, filenames in os.walk(pathname):
        for  filename in filenames:
            if os.path.splitext(filename)[1] == ".py":
                yield os.path.join(dirpath, filename)
Vajk Hermecz
  • 5,413
  • 2
  • 34
  • 25
0

The other answers here will run the code in the package as they inspect it. If you don't want that, you can grep the files like this answer

def _get_class_names(file_name: str) -> List[str]:
    """Get the python class name defined in a file without running code
    file_name: the name of the file to search for class definitions in
    return: all the classes defined in that python file, empty list if no matches"""
    defined_class_names = []
    # search the file for class definitions
    with open(file_name, "r") as file:
        for line in file:
            # regular expression for class defined in the file
            # searches for text that starts with "class" and ends with ( or :,
            # whichever comes first
            match = re.search("^class(.+?)(\(|:)", line) # noqa
            if match:
                # add the cleaned match to the list if there is one
                defined_class_name = match.group(1).strip()
                defined_class_names.append(defined_class_name)
    return defined_class_names
Flair
  • 2,609
  • 1
  • 29
  • 41
jsmith2021
  • 15
  • 4
0

To complete @Metal3d answer, yes you can do testpkg.__loader__.get_resource_reader().contents() to list the "module resources" but it will work only if you imported your package in the "normal" way and your loader is _frozen_importlib_external.SourceFileLoader object.

But if you imported your library with zipimport (ex: to load your package in memory), your loader will be a zipimporter object, and its get_resource_reader function is different from importlib; it will require a "fullname" argument.

To make it work in these two loaders, just specify your package name in argument to get_resource_reader :

# An example with CrackMapExec tool

import importlib

import cme.protocols as cme_protocols


class ProtocolLoader:
    def get_protocols(self):
        protocols = {}
        protocols_names = [x for x in cme_protocols.__loader__.get_resource_reader("cme.protocols").contents()]

        for prot_name in protocols_names:
            prot = importlib.import_module(f"cme.protocols.{prot_name}")
            protocols[prot_name] = prot

        return protocols
mxrch
  • 53
  • 6
-3
def package_contents(package_name):
  package = __import__(package_name)
  return [module_name for module_name in dir(package) if not module_name.startswith("__")]
  • 1
    That only works for modules, not packages. Try it on Python's `logging` package to see what I mean. Logging contains two modules: handlers and config. Your code will return a list of 66 items, which doesn't include those two names. – DNS Mar 25 '09 at 15:26