Static analysis tool that detects errors in Python code without importing it.
—
Advanced code analysis engine that performs AST-based static analysis with scope tracking and binding management. The Checker class provides fine-grained control over the analysis process and access to detailed results, supporting custom analysis workflows and integration with development tools.
The Checker class is the core of pyflakes' analysis engine, implementing a comprehensive visitor pattern for Python AST nodes with sophisticated scope and binding management.
Main analysis engine that walks Python AST nodes and maintains scope information to detect various code issues.
class Checker:
"""I check the cleanliness and sanity of Python code."""
def __init__(self, tree, filename='(none)', builtins=None, withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()):
"""
Initialize the checker with an AST tree.
Parameters:
- tree: AST tree from ast.parse()
- filename (str): Name of the file being checked (for error reporting)
- builtins (set, optional): Additional builtin names to recognize (extends default set)
- withDoctest (bool): Whether to check doctest code blocks (controlled by PYFLAKES_DOCTEST env var)
- file_tokens (tuple): Deprecated parameter for token information (warns when used)
"""
# Core attributes
messages: list # List of Message objects representing found issues
filename: str # Filename being checked
deadScopes: list # List of completed scopes for analysis
scopeStack: list # Stack of active scopes during traversal
exceptHandlers: list # Stack of exception handler names for context
root # Root AST node
nodeDepth: int # Current depth in AST traversal
offset # Line/column offset for node adjustment
withDoctest: bool # Whether doctest processing is enabled
# Properties
@property
def futuresAllowed(self) -> bool:
"""Whether __future__ imports are allowed at current position."""
@property
def annotationsFutureEnabled(self) -> bool:
"""Whether 'from __future__ import annotations' is active."""
@property
def scope(self):
"""Current scope (last item in scopeStack)."""
@property
def _in_postponed_annotation(self) -> bool:
"""Whether currently in a postponed annotation context."""Usage:
import ast
import pyflakes.checker
# Parse Python code into AST
code = """
import os
unused_var = 42
print(undefined_var)
"""
tree = ast.parse(code, filename='example.py')
checker = pyflakes.checker.Checker(tree, filename='example.py')
# Access results
print(f"Found {len(checker.messages)} issues:")
for message in checker.messages:
print(f" {message}")
# Access scope information
print(f"Analyzed {len(checker.deadScopes)} scopes")Primary methods for managing the analysis process and reporting issues.
def report(self, messageClass, *args, **kwargs):
"""Add a message to the results."""
def deferFunction(self, callable):
"""Schedule a function handler to be called just before completion."""
def checkDeadScopes(self):
"""Analyze completed scopes for unused names."""
def handleNode(self, node, parent):
"""Process an AST node during analysis."""
def handleChildren(self, tree, omit=None):
"""Process child nodes with optional omissions."""
def getNodeHandler(self, node_class):
"""Get handler method for AST node type."""
def addBinding(self, node, value):
"""Add name binding to current scope."""Methods for managing Python scopes and their relationships.
def in_scope(self, cls):
"""Context manager for scope management."""
def getScopeNode(self, node):
"""Find scope-defining ancestor."""
def getParent(self, node):
"""Get meaningful parent of a node."""
def getCommonAncestor(self, lnode, rnode, stop):
"""Find common ancestor of two nodes."""
def descendantOf(self, node, ancestors, stop):
"""Check if node is descendant of ancestors."""
def differentForks(self, lnode, rnode):
"""Check if nodes are on different conditional branches."""Methods for handling variable assignments and name resolution.
def handleNodeLoad(self, node, parent):
"""Handle name loading/usage."""
def handleNodeStore(self, node):
"""Handle name storage/assignment."""
def handleNodeDelete(self, node):
"""Handle name deletion."""Methods for handling type annotations and forward references.
def handleAnnotation(self, annotation, node):
"""Process type annotations."""
def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err):
"""Process string annotations."""
def handle_annotation_always_deferred(self, annotation, parent):
"""Always defer annotation processing."""
def _enter_annotation(self, ann_type=AnnotationState.BARE):
"""Context manager for annotations."""The Checker class implements handlers for all Python AST node types. Each handler is named after the AST node type in uppercase.
def FUNCTIONDEF(self, node):
"""Handle function definitions."""
def ASYNCFUNCTIONDEF(self, node):
"""Handle async function definitions."""
def CLASSDEF(self, node):
"""Handle class definitions."""
def RETURN(self, node):
"""Handle return statements with scope validation."""
def YIELD(self, node):
"""Handle yield expressions with scope validation."""
def GLOBAL(self, node):
"""Handle global declarations."""
def IMPORT(self, node):
"""Handle import statements."""
def IMPORTFROM(self, node):
"""Handle 'from X import Y' statements."""
def ASSIGN(self, node):
"""Handle assignments."""
def AUGASSIGN(self, node):
"""Handle augmented assignments (+=, etc.)."""
def ANNASSIGN(self, node):
"""Handle annotated assignments."""
def FOR(self, node):
"""Handle for loops."""
def ASYNCFOR(self, node):
"""Handle async for loops."""
def WHILE(self, node):
"""Handle while loops."""
def IF(self, node):
"""Handle if statements with tuple condition checking."""
def WITH(self, node):
"""Handle with statements."""
def ASYNCWITH(self, node):
"""Handle async with statements."""
def TRY(self, node):
"""Handle try/except blocks with handler tracking."""
def EXCEPTHANDLER(self, node):
"""Handle exception handlers."""
def RAISE(self, node):
"""Handle raise statements with NotImplemented checking."""
def ASSERT(self, node):
"""Handle assert statements with tuple condition checking."""
def CONTINUE(self, node):
"""Handle continue/break statements with context validation."""
def BREAK(self, node):
"""Handle break statements."""
def PASS(self, node):
"""Handle pass statements."""def NAME(self, node):
"""Handle name references (load/store/delete)."""
def CALL(self, node):
"""Handle function calls with format string validation."""
def SUBSCRIPT(self, node):
"""Handle subscript expressions with special typing logic."""
def ATTRIBUTE(self, node):
"""Handle attribute access."""
def BINOP(self, node):
"""Handle binary operations with percent format checking."""
def COMPARE(self, node):
"""Handle comparison operations with literal checking."""
def TUPLE(self, node):
"""Handle tuples with starred expression validation."""
def LIST(self, node):
"""Handle lists."""
def DICT(self, node):
"""Handle dictionaries with duplicate key detection."""
def SET(self, node):
"""Handle sets."""
def CONSTANT(self, node):
"""Handle constants with string annotation support."""
def JOINEDSTR(self, node):
"""Handle f-strings with placeholder validation."""
def TEMPLATESTR(self, node):
"""Handle template strings (t-strings)."""
def LAMBDA(self, node):
"""Handle lambda expressions and function arguments."""
def GENERATOREXP(self, node):
"""Handle generator expressions and comprehensions."""
def LISTCOMP(self, node):
"""Handle list comprehensions."""
def DICTCOMP(self, node):
"""Handle dictionary comprehensions."""
def SETCOMP(self, node):
"""Handle set comprehensions."""def TYPEVAR(self, node):
"""Handle type variable definitions."""
def TYPEALIAS(self, node):
"""Handle type alias definitions."""
def PARAMSPEC(self, node):
"""Handle parameter specifications."""
def TYPEVARTUPLE(self, node):
"""Handle type variable tuples."""def MATCH(self, node):
"""Handle match statements."""
def MATCH_CASE(self, node):
"""Handle match cases."""
def MATCHAS(self, node):
"""Handle match as patterns."""
def MATCHCLASS(self, node):
"""Handle match class patterns."""
def MATCHMAPPING(self, node):
"""Handle match mapping patterns."""
def MATCHOR(self, node):
"""Handle match or patterns."""
def MATCHSEQUENCE(self, node):
"""Handle match sequence patterns."""
def MATCHSINGLETON(self, node):
"""Handle match singleton patterns."""
def MATCHSTAR(self, node):
"""Handle match star patterns."""
def MATCHVALUE(self, node):
"""Handle match value patterns."""Helper methods for common analysis tasks.
def isLiteralTupleUnpacking(self, node) -> bool:
"""Check for literal tuple unpacking."""
def isDocstring(self, node) -> bool:
"""Check if node is a docstring."""
def getDocstring(self, node):
"""Extract docstring content and line number."""
def handleDoctests(self, node):
"""Process doctest examples in docstrings."""
def ignore(self, node):
"""No-op handler for ignored node types."""
def _in_doctest(self) -> bool:
"""Check if currently in doctest scope."""Methods for validating string formatting operations.
def _handle_string_dot_format(self, node):
"""Validate .format() method calls."""
def _handle_percent_format(self, node):
"""Validate % format operations."""The checker implements a deferred analysis system to handle forward references and ensure proper name resolution order.
# Deferred functions are stored and executed after initial traversal
checker._deferred = [] # Queue of deferred function handlers
checker._run_deferred() # Execute all deferred functionsKey Features:
Comprehensive system for tracking name bindings across all scopes using a hierarchical class system.
class Binding:
"""Base class for all name bindings."""
def __init__(self, name, source):
"""
Parameters:
- name (str): The bound name
- source: AST node where binding occurs
"""
name: str # The bound name
source # AST node where binding occurs
used # Usage tracking: False or (scope, node) tuple
def redefines(self, other) -> bool:
"""Determines if this binding redefines another."""
class Definition(Binding):
"""Base class for bindings that define functions or classes."""
def redefines(self, other) -> bool:
"""Can redefine other definitions or assignments."""
class Assignment(Binding):
"""Regular variable assignments (x = value)."""
class NamedExprAssignment(Assignment):
"""Walrus operator assignments (x := value)."""
class Annotation(Binding):
"""Type annotations without values (x: int)."""
def redefines(self, other) -> bool:
"""Annotations don't redefine names."""
class Argument(Binding):
"""Function parameters."""
class FunctionDefinition(Definition):
"""Function definitions (def func():)."""
class ClassDefinition(Definition):
"""Class definitions (class MyClass:)."""
class Builtin(Definition):
"""Built-in names (like print, len)."""class Importation(Definition):
"""Standard import statements (import module)."""
def __init__(self, name, source, full_name=None):
"""
Parameters:
- name (str): Local name for the import
- source: AST node
- full_name (str): Complete import path
"""
fullName: str # Complete import path
redefined: list # List of redefinition nodes
def _has_alias(self) -> bool:
"""Whether import uses 'as' clause."""
@property
def source_statement(self) -> str:
"""Reconstructs the original import statement."""
class SubmoduleImportation(Importation):
"""Submodule imports (import package.module)."""
class ImportationFrom(Importation):
"""From imports (from module import name)."""
class StarImportation(Importation):
"""Star imports (from module import *)."""
class FutureImportation(ImportationFrom):
"""Future imports (from __future__ import feature)."""
class ExportBinding(Binding):
"""__all__ assignments for module exports."""
def __init__(self, name, source, scope):
"""Parses __all__ assignments and concatenations."""
names: list # List of exported names from __all__Bindings are created through several key methods:
Name Storage (handleNodeStore):
Annotation for type-only annotations (x: int)Binding for loop variables and tuple unpackingExportBinding for __all__ assignmentsNamedExprAssignment for walrus operators (x := value)Assignment for regular assignments (x = value)Import Processing:
IMPORT method creates Importation or SubmoduleImportationIMPORTFROM method creates ImportationFrom, StarImportation, or FutureImportationDefinition Processing:
FUNCTIONDEF creates FunctionDefinition bindingsCLASSDEF creates ClassDefinition bindingsARG creates Argument bindings for function parametersBindings track their usage through the used attribute:
False - Never used(scope, node) tuple - Used, with reference to usage locationUsage is recorded in handleNodeLoad(node, parent) when names are accessed.
Manages nested Python scopes with their specific rules and behaviors using a hierarchy of scope classes.
class Scope(dict):
"""Base scope class - dictionary of name -> binding."""
def __init__(self, filename=None):
"""Initialize scope with optional filename."""
def __contains__(self, key):
"""Check if name is bound in this scope."""
def unusedAssignments(self):
"""Return unused assignments in this scope."""
class ModuleScope(Scope):
"""Module-level scope."""
def __init__(self, filename=None):
self.importStarred = False # Whether any star imports exist
self.futureAnnotations = False # Whether __future__ annotations enabled
class ClassScope(Scope):
"""Class definition scope."""
def __init__(self, filename=None):
# Classes don't participate in normal name resolution
pass
class FunctionScope(Scope):
"""Function and method scope."""
def __init__(self, filename=None):
self.globals = {} # Global declarations
self.nonlocals = {} # Nonlocal declarations
self.indirectGlobals = {} # Names that may become global
self.indirectNonlocals = {} # Names that may become nonlocal
def unused_assignments(self):
"""Return unused assignments in function scope."""
def unused_annotations(self):
"""Return unused type annotations in function scope."""
class GeneratorScope(Scope):
"""Generator expressions and comprehensions."""
def __init__(self, filename=None):
# Generator scopes can access class scope variables
pass
class TypeScope(Scope):
"""Type parameter scope (Python 3.12+)."""
class DoctestScope(ModuleScope):
"""Doctest execution scope."""The checker maintains a stack of active scopes during AST traversal:
# Core scope management attributes
scopeStack: list # Stack of active scopes during traversal
deadScopes: list # List of completed scopes for analysis
@property
def scope(self):
"""Current scope (last item in scopeStack)."""
return self.scopeStack[-1] if self.scopeStack else None
def in_scope(self, cls):
"""Context manager for scope management."""
# Pushes new scope, yields it, then pops and moves to deadScopesName Lookup Order:
Special Scoping Rules:
scope.importStarred = True to disable undefined name warningsScopes store bindings and provide methods for analysis:
# Adding bindings to scopes
def addBinding(self, node, value):
"""Add name binding to appropriate scope."""
# Finds correct scope in stack based on global/nonlocal declarations
# Handles redefinition checking and reporting
# Updates binding usage information
# Scope analysis after traversal
def checkDeadScopes(self):
"""Analyze completed scopes for unused names."""
# Reports unused imports, variables, and annotations
# Validates __all__ exports
# Processes star import usage patternsModule Scope Features:
from __future__ import annotations)__all__ export validationFunction Scope Features:
Class Scope Features:
Generator Scope Features:
Handles Python type annotations with support for forward references and from __future__ import annotations.
Annotation States:
BARE - Direct annotation contextSTRINGIZED - String annotation contextPOSTPONED - Deferred annotation contextFeatures:
typing module constructsStructured system for collecting and reporting code issues.
Features:
messages listimport ast
import pyflakes.checker
def custom_analysis_callback():
"""Custom analysis to run after main traversal."""
print("Running custom analysis...")
# Access checker state here
return
code = """
def func():
global_var = 42 # This will be analyzed after global scope
global_var = 10
"""
tree = ast.parse(code, 'test.py')
checker = pyflakes.checker.Checker(tree, 'test.py')
# Add custom deferred analysis
checker.deferFunction(custom_analysis_callback)
# Analysis completes with our custom function
print(f"Found {len(checker.messages)} issues")import ast
import pyflakes.checker
code = """
class MyClass:
x = 1
def method(self):
y = 2
def inner():
z = 3
"""
tree = ast.parse(code, 'test.py')
checker = pyflakes.checker.Checker(tree, 'test.py')
# Analyze completed scopes
print(f"Analyzed {len(checker.deadScopes)} scopes:")
for i, scope in enumerate(checker.deadScopes):
print(f" Scope {i}: {type(scope).__name__}")
print(f" Bindings: {list(scope.keys())}")import ast
import pyflakes.checker
from pyflakes.messages import UnusedImport, UndefinedName
code = """
import os
import sys
print(undefined_name)
unused_var = 42
"""
tree = ast.parse(code, 'test.py')
checker = pyflakes.checker.Checker(tree, 'test.py')
# Categorize messages
unused_imports = [m for m in checker.messages if isinstance(m, UnusedImport)]
undefined_names = [m for m in checker.messages if isinstance(m, UndefinedName)]
print(f"Unused imports: {len(unused_imports)}")
print(f"Undefined names: {len(undefined_names)}")
for msg in checker.messages:
print(f"{type(msg).__name__}: {msg}")The checker provides sophisticated annotation handling that can be extended:
import ast
import pyflakes.checker
code = '''
from typing import List, Dict
from __future__ import annotations
def func(x: List[int]) -> Dict[str, int]:
"""Function with type annotations."""
return {"key": x[0]}
# Forward reference
def forward_ref() -> SomeClass:
pass
class SomeClass:
pass
'''
tree = ast.parse(code, 'test.py')
checker = pyflakes.checker.Checker(tree, 'test.py')
# Check if future annotations are enabled
print(f"Future annotations enabled: {checker.annotationsFutureEnabled}")
# All annotations are processed appropriately
print(f"Analysis complete with {len(checker.messages)} issues")Install with Tessl CLI
npx tessl i tessl/pypi-pyflakes