Beniget: Static Python Code Analysis
Beniget is a static analyzer for Python code, providing compile-time analyses on Python Abstract Syntax Tree (AST). It offers over-approximation of global and local definitions and can compute def-use chains. It serves as a foundational building block for writing static analyzers or compilers for Python, relying on `gast` for cross-version AST abstraction and also supporting the standard library `ast` since version 0.5.0. It is actively maintained with periodic updates.
Warnings
- gotcha Beniget performs static analysis and cannot fully account for dynamic Python constructs like `eval()`, `exec()`, or direct manipulation of `globals()` or `locals()` dictionaries. Such code patterns may lead to incomplete or misleading analysis results.
- breaking Prior to version 0.5.0, `beniget` exclusively relied on the `gast` library for Abstract Syntax Tree (AST) representation, which provided a unified interface across different Python 3 versions. As of `beniget` 0.5.0, it gained the ability to directly utilize Python's standard `ast` module. Code written for older `beniget` versions might implicitly expect `gast`'s AST structure, requiring adjustment for direct `ast` module usage post-0.5.0.
- gotcha The `gast` library is a mandatory runtime dependency for `beniget`, even though `beniget` v0.5.0 now supports the standard `ast` module. `gast` ensures consistent AST behavior across a wide range of Python 3 versions (>=3.6). Failure to install `gast` will lead to import errors if `beniget` (or your code) tries to use it.
Install
-
pip install beniget
Imports
- Ancestors
from beniget import Ancestors
- DefUseChains
from beniget import DefUseChains
- UseDefChains
from beniget import UseDefChains
- ast
import beniget, gast as ast
Quickstart
import beniget
import gast as ast # Use 'import ast' for Python >= 3.6 and beniget >= 0.5.0
# Example: Detect unused imports
code = """from math import cos, sin
print(cos(3))"""
module = ast.parse(code)
duc = beniget.DefUseChains()
duc.visit(module)
# Assuming the first body element is an ImportFrom statement
imported_names = module.body[0].names
for name in imported_names:
# Find the corresponding definition-use chain for the imported name
# Note: `name.name` gives the imported symbol, e.g., 'cos' or 'sin'
ud_chain = duc.chains.get(name)
if ud_chain and not ud_chain.users():
print(f"Unused import: {ud_chain.name()}")