Python pyclbr: Inspect Modules Safely

Published on: August 15, 2026
Reading time: 5 minutes
Source code on screen representing class and function browsing with Python pyclbr

Python pyclbr reads source code and extracts limited information about classes, functions, methods, and nested definitions without importing the module being analyzed. It was designed to provide enough data for code browsers, symbol indexes, lightweight documentation tools, and project explorers.

The key advantage is avoiding module execution. Importing a file may open connections, read environment variables, register plugins, or run arbitrary top-level code. pyclbr works from Python source instead, making it a safer choice for examining unknown modules. Its output is intentionally limited and does not replace a complete AST analysis.

What pyclbr discovers

The modern API, readmodule_ex(), returns a dictionary containing descriptors for functions and classes declared with def, async def, and class. Descriptors include name, source file, module, starting line, parent, and children.

import pyclbr

symbols = pyclbr.readmodule_ex("my_package.service")
for name, descriptor in symbols.items():
    if name == "__path__":
        continue
    print(name, descriptor.file, descriptor.lineno)

The module argument uses import notation rather than an arbitrary source filename.

Provide search paths

The optional path sequence is prepended to sys.path while locating the source.

symbols = pyclbr.readmodule_ex(
    "application.module",
    path=["/workspace/src"],
)

Resolve and restrict these paths to approved project roots. A web interface should never let an external user inspect any filesystem directory.

readmodule and readmodule_ex

readmodule() is the historical interface and returns module-level class descriptors only. readmodule_ex() adds functions, classes, and nested definitions, so new code should normally use it.

classes_only = pyclbr.readmodule("my_module")
complete = pyclbr.readmodule_ex("my_module")

Legacy tools may require the original format, but the extended API provides a more useful symbol tree.

Function descriptors

Function objects expose file, module, name, lineno, parent, children, and is_async.

for name, item in symbols.items():
    if isinstance(item, pyclbr.Function):
        print({
            "name": item.name,
            "line": item.lineno,
            "async": item.is_async,
        })

The is_async flag separates normal functions from coroutine functions declared with async def.

Class descriptors

Class objects include the same core attributes plus super and methods. The super list may contain resolved class descriptors or plain strings when a base class cannot be discovered.

for item in symbols.values():
    if isinstance(item, pyclbr.Class):
        bases = [
            base.name if hasattr(base, "name") else base
            for base in item.super
        ]
        print(item.name, bases, item.methods)

Do not assume that every inheritance relationship will be resolved. Conditional imports, aliases, generated bases, and metaprogramming limit static discovery.

Nested definitions

Descriptors provide children and parent, allowing tools to represent inner classes, methods, and local functions.

def visit(item, depth=0):
    print("  " * depth, item.name, item.lineno)
    for child in item.children.values():
        visit(child, depth + 1)

for item in symbols.values():
    if hasattr(item, "children"):
        visit(item)

If results from multiple modules are combined, consider tracking visited objects to avoid repeated processing.

Build a symbol index

Descriptors can be converted into JSON for a code-search service.

def serialize(item):
    return {
        "module": item.module,
        "name": item.name,
        "file": item.file,
        "line": item.lineno,
        "kind": type(item).__name__,
        "children": [
            serialize(child)
            for child in item.children.values()
        ],
    }

Normalize paths before storage and avoid publishing absolute server paths in API responses.

Because descriptors include file and line number, a browser can open the exact definition. Validate that the resolved file remains under the workspace before generating editor URLs or download links.

To enumerate available modules, combine the approach with Python pkgutil. To map imports used by a script, read the Python modulefinder guide.

Packages and __path__

When the analyzed name is a package, the returned dictionary contains a __path__ key with package search paths.

result = pyclbr.readmodule_ex("my_package")
print(result.get("__path__"))

Handle this key separately because it is not a function or class descriptor.

Why not import the module?

importlib.import_module() combined with inspect provides runtime information, but importing executes top-level code. A malicious, broken, or environment-dependent plugin may cause side effects before inspection starts.

pyclbr avoids that execution by reading source. This does not make arbitrary paths harmless: the tool still opens files and may consume resources on very large trees.

Extension-module limitations

The module analyzes implementations written in Python. C extensions, built-in modules, and some generated modules do not provide compatible Python source. Analysis may fail or return no useful symbols.

Use type stubs, package metadata, or controlled runtime inspection in a separate process when extension modules must be represented.

Static-analysis limitations

Dynamically created classes, functions assigned through expressions, decorators that replace objects, methods injected by metaclasses, and dynamic imports may not appear as expected. pyclbr reports syntactic declarations, not the final runtime state.

Use the ast module for deeper structural analysis. For lexical editor features, see the Python tokenize guide.

Compare with codeop

The Python codeop guide covers compiling interactive text and determining whether input is complete. pyclbr solves a different problem: extracting a limited tree of definitions from a module located through the import system.

Caching and refresh

Indexing tools should detect source changes and read the module again. Store a timestamp, size, or hash with each result. Do not keep stale descriptors indefinitely during active development.

Error handling

Missing files, invalid syntax, encoding problems, and unresolved imported bases can affect results. Catch errors per module and continue indexing other files.

def read_safely(name, paths):
    try:
        return pyclbr.readmodule_ex(name, path=paths)
    except (ImportError, OSError, SyntaxError) as exc:
        record_failure(name, exc)
        return {}

Do not hide every exception silently because the final index may look complete while omitting important modules.

Security and resource limits

  • Restrict source search roots.
  • Do not expose absolute paths.
  • Limit file count and file size.
  • Set a timeout for indexing jobs.
  • Do not follow symbolic links outside the workspace.
  • Run with minimum filesystem permissions.
  • Remember that non-importing is not a complete sandbox.

Testing strategy

Test synchronous and asynchronous functions, classes with multiple inheritance, methods, nested classes, local functions, packages, import aliases, and files with invalid syntax. Verify line numbers and paths on Windows and Unix.

Include decorators and metaclasses to document what your browser can and cannot represent.

  • Prefer readmodule_ex().
  • Handle __path__ separately.
  • Expect unresolved base classes.
  • Normalize and restrict paths.
  • Refresh the index when source changes.
  • Record failures per module.
  • Use AST when complete detail is required.
  • Do not import unknown code merely to list symbols.

Conclusion

Python pyclbr provides a lightweight way to discover functions and classes without executing the target module. It is well suited to code browsers, simple indexes, and tools that need to reduce import side effects.

Use it with clear expectations: Python source only, syntactic declarations, and partial information. Consult the official pyclbr documentation and the Python ast documentation.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Monitor with binary code representing Python bytecode opcode instructions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python opcode: Explore Bytecode

    Learn Python opcode to map bytecode instructions, arguments, jumps, caches, and stack effects through the documented dis APIs.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026
    Developer investigating memory usage with Python tracemalloc
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tracemalloc: Find Memory Leaks

    Use Python tracemalloc to compare snapshots, locate memory growth, and investigate leaks in long-running applications.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026
    Code with type annotations representing introspection with Python annotationlib
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python annotationlib: Read Annotations

    Learn Python annotationlib in 3.14 to retrieve annotations as values, ForwardRef proxies, or strings while controlling execution risks.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Organized archive binders representing modules imported directly from ZIP files with Python zipimport
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipimport: Import from ZIP Files

    Learn Python zipimport to load modules and packages from ZIP archives, work with importers, and protect plugin systems.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Directory diagram representing site-packages paths and Python site module configuration
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python site: Understand Package Paths

    Learn the Python site module to understand site-packages, user site, .pth files, sitecustomize, usercustomize, virtual environments, and startup flags.

    Ler mais

    Tempo de leitura: 6 minutos
    14/08/2026
    Installer icon representing offline pip bootstrap with Python ensurepip
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python ensurepip: Restore pip Offline

    Learn Python ensurepip to install or restore pip offline, choose the environment, script names, upgrade behavior, and avoid system conflicts.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026