Python's dynamic nature makes dead code detection harder than in statically-typed languages. Functions can be called via getattr(), classes instantiated from strings, and modules imported dynamically. Vulture handles this by using AST analysis with confidence scoring.
What Vulture Detects
Vulture parses Python files into abstract syntax trees and reports:
- Unused functions -- defined with
defbut never called - Unused classes -- defined with
classbut never instantiated or subclassed - Unused imports --
importorfrom ... importstatements where the imported name is never used - Unused variables -- assigned but never read
- Unused properties -- class attributes that are never accessed
- Unreachable code -- code after
return,break,continue, orraisestatements
Installing Vulture
pip install vulture
Running a Scan
Basic usage:
vulture src/
With confidence threshold (recommended):
vulture src/ --min-confidence 80
The --min-confidence flag filters out findings where Vulture is less certain the code is truly unused. A threshold of 80 is a good starting point -- it catches obvious dead code while filtering most false positives.
Understanding Confidence Scores
Vulture assigns a confidence percentage to each finding:
- 100% -- definitely unused (e.g., a function defined in a module that is never imported)
- 60-90% -- likely unused but could be called dynamically
- Below 60% -- might be used via dynamic patterns
src/utils/legacy.py:15: unused function 'calculate_discount' (confidence: 100%)
src/models/user.py:42: unused variable 'cache_key' (confidence: 90%)
src/api/handlers.py:8: unused import 'json' (confidence: 60%)
Start with --min-confidence 80 and lower it as you get comfortable with the tool.
Handling False Positives
Python's dynamic features cause specific false positive patterns:
Django/Flask URL Routing
View functions referenced in URL patterns are not directly imported:
# urls.py
urlpatterns = [
path("users/", views.user_list), # Vulture sees this as a reference
]
# But if the view is in a different file and only referenced by string:
path("api/users/", "myapp.views.user_list"), # Vulture cannot trace this
Decorators and Metaclasses
@app.route("/api/health") # Vulture may not trace the decorator's registration
def health_check():
return {"status": "ok"}
Dynamic Attribute Access
handler_name = f"handle_{event_type}"
handler = getattr(self, handler_name) # Vulture cannot trace this
handler()
Creating a Whitelist
For known false positives, create a whitelist file:
# whitelist.py
# These are used dynamically and should not be flagged
# Django views referenced by URL strings
user_list # noqa
user_detail # noqa
# Signal handlers
on_user_created # noqa
Run Vulture with the whitelist:
vulture src/ whitelist.py --min-confidence 80
Using Vulture with CleanAI
CleanAI integrates Vulture for Python projects:
- Open your Python project in VS Code or Cursor
- Run "CleanAI: Analyze Codebase"
- CleanAI detects Python files and runs Vulture
- Results appear in the webview panel with confidence scores
- Remove findings one-by-one or use Auto Clean (Safe)
CleanAI filters low-confidence findings by default and lets you adjust the threshold in settings.
CI Integration
Add Vulture to your CI pipeline:
vulture src/ whitelist.py --min-confidence 80
The command exits with code 1 if any findings are detected, failing the CI check.
Best Practices
- Start with high confidence -- use
--min-confidence 90initially, then lower as you clean up - Maintain a whitelist -- add legitimate dynamic patterns to avoid repeated false positives
- Scan regularly -- Python dead code accumulates quickly in large projects
- Combine with type hints -- typed Python code produces fewer false positives because Vulture can trace references more accurately
Getting Started
Install Vulture (pip install vulture) for command-line scanning, or install CleanAI for visual scanning with safe removal in VS Code and Cursor.