or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/pypi-flake8-simplify

A flake8 plugin that helps simplify Python code by detecting patterns that can be improved for readability and maintainability

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/flake8-simplify@0.22.x

To install, run

npx @tessl/cli install tessl/pypi-flake8-simplify@0.22.0

index.mddocs/

flake8-simplify

A flake8 plugin that helps simplify Python code by detecting patterns that can be improved for readability and maintainability. It provides over 40 different rules that analyze Python AST and suggest code simplifications, including combining isinstance calls, using context managers, replacing nested if statements, utilizing built-in functions, and modernizing Python constructs.

Package Information

  • Package Name: flake8-simplify
  • Language: Python
  • Installation: pip install flake8-simplify
  • Python Version: 3.6.1+
  • Dependencies: astor>=0.1, flake8>=3.7, importlib-metadata>=0.9 (Python < 3.8)

Core Imports

The plugin automatically registers with flake8 through entry points (registered as "SIM") and doesn't require explicit imports in typical usage scenarios. Users simply install the package and run flake8.

For direct programmatic access to the plugin class (advanced usage only):

from flake8_simplify import Plugin

However, the standard workflow requires no imports - the plugin integrates automatically via the entry point defined in pyproject.toml:

[project.entry-points]
"flake8.extension" = {SIM = "flake8_simplify:Plugin"}

Basic Usage

flake8-simplify integrates seamlessly with flake8. After installation, it automatically analyzes your code when running flake8:

# Install the plugin
pip install flake8-simplify

# Run flake8 on your code
flake8 your_file.py

# Or run on entire directory
flake8 .

Example output:

./foo.py:10:12: SIM101 Multiple isinstance-calls which can be merged into a single call for variable 'value'
./bar.py:25:8: SIM108 Use ternary operator instead of if-else-assignment
./baz.py:42:1: SIM115 Use context handler for opening files

Architecture

The plugin follows flake8's standard plugin architecture:

  • Plugin: Main entry point that flake8 discovers and instantiates
  • Visitor: AST visitor that traverses Python code and applies all rules
  • Rule Functions: Individual functions implementing specific simplification checks
  • Utility Classes: Extended AST node classes with metadata for rule analysis

Each rule function analyzes specific AST node types and returns violations as tuples containing line number, column offset, and error message. This design allows the plugin to efficiently check multiple simplification patterns in a single AST traversal.

Capabilities

Core Plugin API

The main plugin interface for flake8 integration, providing the entry point for code analysis and rule execution.

class Plugin:
    """Main flake8 plugin class."""
    name = __name__  # Class attribute: Plugin identifier (__name__ resolves to "flake8_simplify")
    version = importlib_metadata.version(__name__)  # Class attribute: Plugin version from package metadata
    
    def __init__(self, tree: ast.AST):
        """
        Initialize plugin with AST tree to analyze.
        
        Parameters:
        - tree: ast.AST - Python AST tree to analyze
        """
    
    def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
        """
        Execute analysis and yield violations.
        
        Returns:
        Generator yielding tuples of (line, column, message, plugin_type)
        """

class Visitor(ast.NodeVisitor):
    """AST visitor implementing all simplification rules."""
    errors: List[Tuple[int, int, str]]  # Collected violations
    
    def __init__(self):
        """Initialize visitor with empty error list."""
    
    # Visit methods for each AST node type
    def visit_Assign(self, node: ast.Assign) -> Any: ...
    def visit_Call(self, node: ast.Call) -> Any: ...
    def visit_With(self, node: ast.With) -> Any: ...
    def visit_Expr(self, node: ast.Expr) -> None: ...
    def visit_BoolOp(self, node: ast.BoolOp) -> None: ...
    def visit_If(self, node: ast.If) -> None: ...
    def visit_For(self, node: ast.For) -> None: ...
    def visit_Subscript(self, node: ast.Subscript) -> None: ...
    def visit_Try(self, node: ast.Try) -> None: ...
    def visit_UnaryOp(self, node_v: ast.UnaryOp) -> None: ...
    def visit_IfExp(self, node: ast.IfExp) -> None: ...
    def visit_Compare(self, node: ast.Compare) -> None: ...
    def visit_ClassDef(self, node: ast.ClassDef) -> None: ...

def add_meta(root: ast.AST, level: int = 0) -> None:
    """
    Add parent and sibling metadata to AST nodes.
    
    Adds parent, previous_sibling, and next_sibling attributes to all AST nodes
    to enable rule analysis that requires knowledge of node relationships.
    
    Parameters:
    - root: ast.AST - Root AST node to process
    - level: int - Recursion depth (default: 0)
    """

Assignment Rules

Rules for detecting and simplifying variable assignment patterns.

def get_sim904(node: ast.Assign) -> List[Tuple[int, int, str]]:
    """
    SIM904: Dict assignment that can be simplified.
    
    Detects: a = {}; a['key'] = 'value'
    Suggests: a = {'key': 'value'}
    
    Parameters:
    - node: ast.Assign - Assignment node to analyze
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim909(node: Assign) -> List[Tuple[int, int, str]]:
    """
    SIM909: Remove reflexive assignments.
    
    Detects: foo = foo
    Suggests: Remove redundant assignment
    
    Parameters:
    - node: Assign - Extended assignment node with metadata
    
    Returns:
    List of (line, column, message) tuples
    """

Boolean Operation Rules

Rules for simplifying boolean expressions and logical operations.

def get_sim101(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
    """
    SIM101: Merge multiple isinstance calls.
    
    Detects: isinstance(a, int) or isinstance(a, float)
    Suggests: isinstance(a, (int, float))
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim109(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
    """
    SIM109: Use tuple for multiple equality checks.
    
    Detects: value == a or value == b or value == c  
    Suggests: value in (a, b, c)
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim220(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
    """
    SIM220: Replace contradictory expressions with False.
    
    Detects: a and not a
    Suggests: False
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim221(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
    """
    SIM221: Replace tautological expressions with True.
    
    Detects: a or not a
    Suggests: True
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim222(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
    """
    SIM222: Simplify OR with True.
    
    Detects: ... or True
    Suggests: True
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim223(node: ast.BoolOp) -> List[Tuple[int, int, str]]:
    """
    SIM223: Simplify AND with False.
    
    Detects: ... and False
    Suggests: False
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node
    
    Returns:
    List of (line, column, message) tuples
    """

Function Call Rules

Rules for optimizing function calls and method invocations.

def get_sim115(node: Call) -> List[Tuple[int, int, str]]:
    """
    SIM115: Use context manager for file operations.
    
    Detects: f = open('file.txt'); ...
    Suggests: with open('file.txt') as f: ...
    
    Parameters:
    - node: Call - Extended call node with metadata
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim901(node: ast.Call) -> List[Tuple[int, int, str]]:
    """
    SIM901: Remove unnecessary bool() wrapper.
    
    Detects: bool(a == b)
    Suggests: a == b
    
    Parameters:
    - node: ast.Call - Function call node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim905(node: ast.Call) -> List[Tuple[int, int, str]]:
    """
    SIM905: Use list literal instead of string split.
    
    Detects: "a,b,c".split(",")
    Suggests: ["a", "b", "c"]
    
    Parameters:
    - node: ast.Call - Function call node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim906(node: ast.Call) -> List[Tuple[int, int, str]]:
    """
    SIM906: Merge nested os.path.join calls.
    
    Detects: os.path.join(os.path.join(a, b), c)
    Suggests: os.path.join(a, b, c)
    
    Parameters:
    - node: ast.Call - Function call node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim910(node: Call) -> List[Tuple[int, int, str]]:
    """
    SIM910: Remove explicit None default from dict.get.
    
    Detects: dict.get(key, None)
    Suggests: dict.get(key)
    
    Parameters:
    - node: Call - Extended call node with metadata
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim911(node: ast.Call) -> List[Tuple[int, int, str]]:
    """
    SIM911: Use dict.items() instead of zip(keys(), values()).
    
    Detects: zip(dict.keys(), dict.values())
    Suggests: dict.items()
    
    Parameters:
    - node: ast.Call - Function call node
    
    Returns:
    List of (line, column, message) tuples
    """

Control Flow Rules

Rules for simplifying if statements, loops, and conditional expressions.

def get_sim102(node: ast.If) -> List[Tuple[int, int, str]]:
    """
    SIM102: Combine nested if statements.
    
    Detects: if a: if b: ...
    Suggests: if a and b: ...
    
    Parameters:
    - node: ast.If - If statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim103(node: ast.If) -> List[Tuple[int, int, str]]:
    """
    SIM103: Return boolean condition directly.
    
    Detects: if condition: return True else: return False
    Suggests: return condition
    
    Parameters:
    - node: ast.If - If statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim108(node: If) -> List[Tuple[int, int, str]]:
    """
    SIM108: Use ternary operator for assignments.
    
    Detects: if condition: x = a else: x = b
    Suggests: x = a if condition else b
    
    Parameters:
    - node: If - Extended if node with metadata
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim114(node: ast.If) -> List[Tuple[int, int, str]]:
    """
    SIM114: Combine if-elif with same body using OR.
    
    Detects: if a: body elif b: body
    Suggests: if a or b: body
    
    Parameters:
    - node: ast.If - If statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim116(node: ast.If) -> List[Tuple[int, int, str]]:
    """
    SIM116: Use dictionary instead of if-elif equality checks.
    
    Detects: Long if-elif chains with equality comparisons
    Suggests: Dictionary lookup pattern
    
    Parameters:
    - node: ast.If - If statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim401(node: ast.If) -> List[Tuple[int, int, str]]:
    """
    SIM401: Use dict.get() with default instead of if-else.
    
    Detects: if key in dict: x = dict[key] else: x = default
    Suggests: x = dict.get(key, default)
    
    Parameters:
    - node: ast.If - If statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim908(node: ast.If) -> List[Tuple[int, int, str]]:
    """
    SIM908: Use dict.get() for key existence checks.
    
    Detects: if key in dict: dict[key]
    Suggests: dict.get(key)
    
    Parameters:
    - node: ast.If - If statement node
    
    Returns:
    List of (line, column, message) tuples
    """

Loop Rules

Rules for optimizing for loops and replacing them with more Pythonic constructs.

def get_sim104(node: ast.For) -> List[Tuple[int, int, str]]:
    """
    SIM104: Use yield from for generator delegation.
    
    Detects: for item in iterable: yield item
    Suggests: yield from iterable
    
    Parameters:
    - node: ast.For - For loop node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim110_sim111(node: ast.For) -> List[Tuple[int, int, str]]:
    """
    SIM110/SIM111: Use any() or all() for loop patterns.
    
    SIM110 - Detects: for x in iterable: if condition: return True
    SIM110 - Suggests: return any(condition for x in iterable)
    
    SIM111 - Detects: for x in iterable: if condition: return False  
    SIM111 - Suggests: return all(not condition for x in iterable)
    
    Parameters:
    - node: ast.For - For loop node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim113(node: For) -> List[Tuple[int, int, str]]:
    """
    SIM113: Use enumerate instead of manual counter.
    
    Detects: i = 0; for item in iterable: ...; i += 1
    Suggests: for i, item in enumerate(iterable): ...
    
    Parameters:
    - node: For - Extended for node with metadata
    
    Returns:
    List of (line, column, message) tuples
    """

Comparison and Unary Operation Rules

Rules for simplifying comparison operations and unary expressions.

def get_sim118(node: ast.Compare) -> List[Tuple[int, int, str]]:
    """
    SIM118: Use 'key in dict' instead of 'key in dict.keys()'.
    
    Detects: key in dict.keys()
    Suggests: key in dict
    
    Parameters:
    - node: ast.Compare - Comparison node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim300(node: ast.Compare) -> List[Tuple[int, int, str]]:
    """
    SIM300: Use natural comparison order (avoid Yoda conditions).
    
    Detects: 42 == age
    Suggests: age == 42
    
    Parameters:
    - node: ast.Compare - Comparison node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim201(node: UnaryOp) -> List[Tuple[int, int, str]]:
    """
    SIM201: Use '!=' instead of 'not =='.
    
    Detects: not (a == b)
    Suggests: a != b
    
    Parameters:
    - node: UnaryOp - Extended unary operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim202(node: UnaryOp) -> List[Tuple[int, int, str]]:
    """
    SIM202: Use '==' instead of 'not !='.
    
    Detects: not (a != b)
    Suggests: a == b
    
    Parameters:
    - node: UnaryOp - Extended unary operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim203(node: UnaryOp) -> List[Tuple[int, int, str]]:
    """
    SIM203: Use 'not in' instead of 'not (in)'.
    
    Detects: not (a in b)
    Suggests: a not in b
    
    Parameters:
    - node: UnaryOp - Extended unary operation node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim208(node: UnaryOp) -> List[Tuple[int, int, str]]:
    """
    SIM208: Remove double negation.
    
    Detects: not (not a)
    Suggests: a
    
    Parameters:
    - node: UnaryOp - Extended unary operation node
    
    Returns:
    List of (line, column, message) tuples
    """

Ternary Expression Rules

Rules for simplifying conditional expressions (ternary operators).

def get_sim210(node: ast.IfExp) -> List[Tuple[int, int, str]]:
    """
    SIM210: Use bool() instead of True if condition else False.
    
    Detects: True if condition else False
    Suggests: bool(condition)
    
    Parameters:
    - node: ast.IfExp - Ternary expression node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim211(node: ast.IfExp) -> List[Tuple[int, int, str]]:
    """
    SIM211: Use 'not condition' instead of False if condition else True.
    
    Detects: False if condition else True
    Suggests: not condition
    
    Parameters:
    - node: ast.IfExp - Ternary expression node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim212(node: ast.IfExp) -> List[Tuple[int, int, str]]:
    """
    SIM212: Use natural order for ternary expressions.
    
    Detects: b if not a else a
    Suggests: a if a else b
    
    Parameters:
    - node: ast.IfExp - Ternary expression node
    
    Returns:
    List of (line, column, message) tuples
    """

Exception Handling and Context Manager Rules

Rules for improving exception handling and resource management patterns.

def get_sim105(node: ast.Try) -> List[Tuple[int, int, str]]:
    """
    SIM105: Use contextlib.suppress for try-except-pass.
    
    Detects: try: risky_operation() except SomeException: pass
    Suggests: from contextlib import suppress; with suppress(SomeException): risky_operation()
    
    Parameters:
    - node: ast.Try - Try statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim107(node: ast.Try) -> List[Tuple[int, int, str]]:
    """
    SIM107: Avoid return in try/except and finally.
    
    Detects: return statements in both try/except and finally blocks
    Suggests: Restructure control flow to avoid confusing returns
    
    Parameters:
    - node: ast.Try - Try statement node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim117(node: ast.With) -> List[Tuple[int, int, str]]:
    """
    SIM117: Merge nested with statements.
    
    Detects: with a: with b: ...
    Suggests: with a, b: ...
    
    Parameters:
    - node: ast.With - With statement node
    
    Returns:
    List of (line, column, message) tuples
    """

Class and Type Rules

Rules for modernizing class definitions and type annotations.

def get_sim120(node: ast.ClassDef) -> List[Tuple[int, int, str]]:
    """
    SIM120: Use modern class definition syntax.
    
    Detects: class FooBar(object):
    Suggests: class FooBar:
    
    Parameters:
    - node: ast.ClassDef - Class definition node
    
    Returns:
    List of (line, column, message) tuples
    """

def get_sim907(node: ast.Subscript) -> List[Tuple[int, int, str]]:
    """
    SIM907: Use Optional[Type] instead of Union[Type, None].
    
    Detects: Union[str, None]
    Suggests: Optional[str]
    
    Parameters:
    - node: ast.Subscript - Subscript node (for type annotations)
    
    Returns:
    List of (line, column, message) tuples
    """

Environment and Expression Rules

Rules for improving environment variable usage and expression patterns.

def get_sim112(node: ast.Expr) -> List[Tuple[int, int, str]]:
    """
    SIM112: Use CAPITAL environment variable names.
    
    Detects: os.environ['path']
    Suggests: os.environ['PATH']
    
    Parameters:
    - node: ast.Expr - Expression node
    
    Returns:
    List of (line, column, message) tuples
    """

Utility Classes and Functions

Extended AST Node Classes

Enhanced AST node classes that include parent and sibling relationships for rule analysis.

class UnaryOp(ast.UnaryOp):
    """Extended UnaryOp with parent tracking."""
    parent: ast.Expr  # Parent node reference
    
    def __init__(self, orig: ast.UnaryOp):
        """Copy constructor from original UnaryOp node."""

class Call(ast.Call):
    """Extended Call with parent tracking."""
    parent: ast.Expr  # Parent node reference
    
    def __init__(self, orig: ast.Call):
        """Copy constructor from original Call node."""

class If(ast.If):
    """Extended If with parent tracking."""
    parent: ast.Expr  # Parent node reference
    
    def __init__(self, orig: ast.If):
        """Copy constructor from original If node."""

class For(ast.For):
    """Extended For with parent/sibling tracking."""
    parent: ast.AST  # Parent node reference
    previous_sibling: ast.AST  # Previous sibling node
    
    def __init__(self, orig: ast.For):
        """Copy constructor from original For node."""

class Assign(ast.Assign):
    """Extended Assign with parent/sibling tracking."""
    parent: ast.AST  # Parent node reference
    previous_sibling: ast.AST  # Previous sibling node
    orig: ast.Assign  # Original node reference
    
    def __init__(self, orig: ast.Assign):
        """Copy constructor from original Assign node."""

Code Analysis Utilities

Helper functions for analyzing and transforming AST nodes.

def to_source(node: Union[None, ast.expr, ast.Expr, ast.withitem, ast.slice, ast.Assign]) -> str:
    """
    Convert AST node to source code string.
    
    Parameters:
    - node: AST node or None
    
    Returns:
    Source code string representation
    """

def strip_parenthesis(string: str) -> str:
    """
    Remove surrounding parentheses from string.
    
    Parameters:
    - string: String to process
    
    Returns:
    String without outer parentheses
    """

def strip_triple_quotes(string: str) -> str:
    """
    Convert triple quotes to double quotes.
    
    Parameters:
    - string: String with potential triple quotes
    
    Returns:
    String with normalized quotes
    """

def use_double_quotes(string: str) -> str:
    """
    Convert single quotes to double quotes.
    
    Parameters:
    - string: String with potential single quotes
    
    Returns:
    String with double quotes
    """

def is_body_same(body1: List[ast.stmt], body2: List[ast.stmt]) -> bool:
    """
    Check if two statement lists are equivalent.
    
    Parameters:
    - body1: First list of AST statements
    - body2: Second list of AST statements
    
    Returns:
    True if equivalent, False otherwise
    """

def is_stmt_equal(a: ast.stmt, b: ast.stmt) -> bool:
    """
    Deep comparison of AST statements.
    
    Parameters:
    - a: First AST statement
    - b: Second AST statement
    
    Returns:
    True if equal, False otherwise
    """

def get_if_body_pairs(node: ast.If) -> List[Tuple[ast.expr, List[ast.stmt]]]:
    """
    Extract condition-body pairs from if-elif chain.
    
    Parameters:
    - node: If statement node
    
    Returns:
    List of (condition, body) tuples
    """

def is_constant_increase(expr: ast.AugAssign) -> bool:
    """
    Check if augmented assignment increments by constant 1.
    
    Parameters:
    - expr: Augmented assignment node
    
    Returns:
    True if increments by 1, False otherwise
    """

def is_exception_check(node: ast.If) -> bool:
    """
    Check if if-statement is an exception check pattern.
    
    Parameters:
    - node: If statement node
    
    Returns:
    True if raises exception, False otherwise
    """

def is_same_expression(a: ast.expr, b: ast.expr) -> bool:
    """
    Check if two expressions are identical.
    
    Parameters:
    - a: First expression node
    - b: Second expression node
    
    Returns:
    True if same, False otherwise
    """

def expression_uses_variable(expr: ast.expr, var: str) -> bool:
    """
    Check if expression references a variable.
    
    Parameters:
    - expr: Expression node
    - var: Variable name to check
    
    Returns:
    True if variable is used, False otherwise
    """

def body_contains_continue(stmts: List[ast.stmt]) -> bool:
    """
    Check if statement list contains continue statements.
    
    Parameters:
    - stmts: List of statements
    
    Returns:
    True if contains continue, False otherwise
    """

def _get_duplicated_isinstance_call_by_node(node: ast.BoolOp) -> List[str]:
    """
    Get a list of isinstance arguments which could be shortened (used by SIM101).
    
    Analyzes boolean operations to find isinstance calls that can be combined.
    For example: isinstance(a, int) or isinstance(a, float) can become isinstance(a, (int, float))
    
    Parameters:
    - node: ast.BoolOp - Boolean operation node containing isinstance calls
    
    Returns:
    List of variable names that have multiple isinstance calls
    """

Constants

Python version compatibility constants for different AST node types. These constants handle differences between Python versions in AST node representation.

# ast.Constant in Python 3.8+, ast.NameConstant in Python 3.6-3.7
if hasattr(ast, 'NameConstant'):
    BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant)
    AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num)
    STR_TYPES = (ast.Constant, ast.Str)
else:
    BOOL_CONST_TYPES = (ast.Constant,)
    AST_CONST_TYPES = (ast.Constant,)
    STR_TYPES = (ast.Constant,)

Types

from typing import Any, Generator, List, Tuple, Type, Union, DefaultDict
from collections import defaultdict
import ast
import itertools
import astor
import importlib_metadata  # For Python < 3.8 compatibility

# Type aliases for rule function return values
RuleViolation = Tuple[int, int, str]  # (line, column, message)
RuleResult = List[RuleViolation]

# Extended AST node types with metadata
ExtendedASTNode = Union[UnaryOp, Call, If, For, Assign]