inspect.ispackage: Identify Python Packages

Published on: October 5, 2026
Reading time: 5 minutes
Python code on screen representing module and package inspection

inspect.ispackage is a Python introspection helper that checks whether a module object represents a package. The distinction matters in documentation generators, plugin discovery systems, code analyzers, IDE tooling, import diagnostics, and applications that walk module trees dynamically.

This guide explains what Python considers a package, how to use inspect.ispackage, how to preserve compatibility with older Python versions, and how to avoid common mistakes involving imports, namespace packages, package metadata, and untrusted extensions.

What is a Python package?

A package is a module that can contain submodules or subpackages. Traditional packages are directories with an __init__.py file, while namespace packages may span multiple directories and do not necessarily include that file. Imported package objects expose information through attributes such as __spec__, __package__, and usually __path__.

Before a dedicated helper existed, many tools classified packages by checking for __path__. That works in many environments, but it spreads import-system details throughout a project. A named inspection function communicates intent more clearly and gives the standard library room to handle edge cases consistently.

Basic usage

import inspect
import email
import math

print(inspect.ispackage(email))
print(inspect.ispackage(math))

The function returns true for package module objects and false for ordinary modules and unrelated objects. It expects an object, not a string containing an import name.

Importing a name before classification

import importlib
import inspect


def is_package_name(name):
    module = importlib.import_module(name)
    return inspect.ispackage(module)

print(is_package_name("json"))
print(is_package_name("math"))

Importing code may execute package initialization. Do not accept arbitrary user-provided names in a privileged process without validation. Plugin platforms should restrict allowed namespaces, isolate risky extensions, and log import failures clearly.

Supporting older Python versions

A library that supports runtimes without inspect.ispackage can keep a compatibility wrapper in one place.

import inspect


def is_package(obj):
    helper = getattr(inspect, "ispackage", None)
    if helper is not None:
        return helper(obj)
    return inspect.ismodule(obj) and hasattr(obj, "__path__")

Centralizing this fallback is better than repeating version checks across modules. It also makes tests and future cleanup easier.

Module versus package

Every imported package is a module object, but not every module is a package. Therefore, inspect.ismodule cannot answer whether an object can contain importable children.

import inspect
import pathlib
import statistics

for item in (pathlib, statistics):
    print(item.__name__)
    print("module:", inspect.ismodule(item))
    print("package:", inspect.ispackage(item))

This distinction helps tools decide whether they should inspect only members of one module or continue walking a hierarchy.

Discovering children with pkgutil

import inspect
import pkgutil
import email

if inspect.ispackage(email):
    for info in pkgutil.iter_modules(email.__path__):
        print(info.name, info.ispkg)

The explicit package check prevents accidental access to __path__ on a plain module. Discovery can list candidate names without importing every child, which reduces startup cost and avoids unnecessary side effects.

Building a plugin registry

import importlib
import inspect
import pkgutil


def discover_plugins(root_name):
    root = importlib.import_module(root_name)
    if not inspect.ispackage(root):
        raise TypeError(f"{root_name!r} is not a package")

    prefix = root.__name__ + "."
    names = []
    for info in pkgutil.iter_modules(root.__path__, prefix):
        if info.name.endswith("_plugin"):
            names.append(info.name)
    return sorted(names)

Keep discovery separate from activation. First identify candidates, then validate configuration and import only the plugins you intend to run. This design produces clearer errors and a smaller attack surface.

Documentation and API explorers

A documentation tool can use inspect.ispackage to decide whether recursive traversal is appropriate. It should still apply depth limits, exclude private modules when configured, and tolerate optional dependencies that are unavailable on the current operating system.

Importing every discovered module is not always safe. Some modules open resources, register signal handlers, inspect hardware, or require native libraries. Prefer static metadata when possible, and isolate dynamic inspection when packages are not trusted.

Namespace packages

Namespace packages are an important reason to prefer import-system semantics over assumptions about directories. A package may have several search locations and no __init__.py. Tools that only inspect the file system may classify it incorrectly or miss part of the namespace.

When walking a namespace package, remember that its search paths can come from multiple distributions. Deduplicate results and use fully qualified names.

Installed distribution versus imported package

inspect.ispackage classifies an in-memory module object. It does not check whether a distribution is installed, retrieve its package-manager version, or map import names to project names. Use importlib.metadata for distribution metadata.

from importlib.metadata import version, PackageNotFoundError

try:
    print(version("requests"))
except PackageNotFoundError:
    print("distribution not installed")

An import name and a distribution name may differ, and one distribution can provide several import packages. Keeping these concepts separate prevents incorrect dependency reports.

Error handling

import importlib
import inspect


def describe_import(name):
    try:
        module = importlib.import_module(name)
    except ModuleNotFoundError as exc:
        if exc.name == name:
            return {"name": name, "found": False}
        return {"name": name, "found": True, "dependency_error": str(exc)}
    except Exception as exc:
        return {"name": name, "found": True, "error": str(exc)}

    return {
        "name": name,
        "found": True,
        "package": inspect.ispackage(module),
    }

A missing internal dependency is different from a missing root module. Preserve that information instead of reporting every import failure as “not installed.”

Security boundaries

A package classification is not a trust decision. A valid package can execute arbitrary Python code during import. For extension systems, use allowlists, signed artifacts where appropriate, limited permissions, subprocess isolation, resource limits, and explicit administrator approval.

Testing strategy

Tests should cover a plain module, a regular package, and a namespace package if your application supports them. Test the compatibility wrapper separately. When testing plugin discovery, use temporary package structures and verify deterministic ordering.

Avoid relying on whatever third-party packages happen to be installed on the test machine. Controlled fixtures produce repeatable results in local development and CI.

Performance considerations

The package check itself is inexpensive. Importing and recursively exploring a large tree can be expensive. Cache results only when profiling shows a real benefit, and define invalidation behavior for development environments that reload modules.

Limit recursion, skip known heavy namespaces, and separate a fast discovery phase from an optional deep-inspection phase.

Practical best practices

Use fully qualified names, sort discovered results, centralize compatibility code, keep discovery separate from execution, and report import errors with context. Do not treat file-system layout as the only source of truth. Do not use package status as proof of installation health or security.

Read the Academify guides on Python modules and packages, importlib, virtual environments, and publishing Python packages. See the official inspect documentation and the Python import system reference.

Conclusion

inspect.ispackage gives Python tools a clear, standard way to distinguish packages from ordinary modules. It improves plugin discovery, documentation traversal, diagnostics, and module analysis. Use it on imported objects, add a contained fallback for older runtimes, and keep package classification separate from distribution metadata, authorization, and trust.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Laptop displaying code and performance graphs for Python sys._jit analysis
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys._jit: Detect and Measure Experimental JIT

    Learn Python sys._jit to detect experimental JIT support, benchmark it correctly, and avoid fragile runtime decisions.

    Ler mais

    Tempo de leitura: 5 minutos
    05/10/2026
    Numerical precision visualization for Python math.fma calculations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    math.fma: Fused Multiply-Add with One Rounding

    Learn Python math.fma for fused multiply-add calculations with one rounding step and better numerical stability.

    Ler mais

    Tempo de leitura: 6 minutos
    04/10/2026
    Developer working on Windows drive automation with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.listdrives: List Windows Drives in Python

    Learn how to list Windows drives with os.listdrives and handle paths, removable media, and errors safely.

    Ler mais

    Tempo de leitura: 4 minutos
    04/10/2026
    Binary code representing Python Buffer protocol
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    collections.abc.Buffer: Type Binary Data Safely

    Learn Python collections.abc.Buffer for typing binary data, using memoryview, reducing copies, and handling memory safely.

    Ler mais

    Tempo de leitura: 5 minutos
    03/10/2026
    Developer configuring structured logs with Python LoggerAdapter merge_extra
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    LoggerAdapter merge_extra: Dynamic Log Context

    Learn Python LoggerAdapter merge_extra for combining persistent context and per-call fields in safe structured logs.

    Ler mais

    Tempo de leitura: 5 minutos
    03/10/2026
    Laptop screen with code for TLS analysis using Python ssl keylog_filename
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    ssl keylog_filename: Inspect TLS in Wireshark

    Learn Python ssl keylog_filename to inspect authorized TLS sessions in Wireshark without disabling encryption or certificate validation.

    Ler mais

    Tempo de leitura: 5 minutos
    02/10/2026