perflint

repository·main·Indexed 20 days ago

https://github.com/tonybaloney/perflint

A Python linter and Pylint plugin designed to detect performance anti-patterns. It identifies computationally expensive or inefficient code structures through checkers such as ForLoopChecker, LoopInvariantChecker, ListChecker, and ComprehensionChecker. It detects issues including unnecessary list casts, loop-invariant statements, inefficient global name usage in loops, and suboptimal iteration methods, providing specific warning codes like W8101, W8201, and W8401.

Tokens
4.2K
Snippets
21
Records
23
Agent score
70%

What's inside perflint

  1. Configure perflint in VS Code

    main

    To enable perflint within VS Code, update your .vscode/settings.json to enable Pylint and pass perflint as a plugin via pylintArgs. It is recommended to point to your .pylintrc file using the --rcfile flag.

    {
        "python.linting.pylintEnabled": true,
        "python.linting.enabled": true,
        "python.linting.pylintArgs": [
            "--load-plugins",
            "perflint",
            "--rcfile",
            "${workspaceFolder}/.pylintrc"
        ],
    }
  2. Reference: perflint performance rules

    main

    The following rules are provided by perflint to identify performance anti-patterns in Python code.

    W8101 : Unnecessary `list()` on already iterable type (`unnecessary-list-cast`)
    W8102: Incorrect iterator method for dictionary (`incorrect-dictionary-iterator`)
    W8201: Loop invariant statement (`loop-invariant-statement`)
    W8202: Global name usage in a loop (`loop-global-usage`)
    R8203 : Try..except blocks have a significant overhead. Avoid using them inside a loop (`loop-try-except-usage`)
    W8204 : Looped slicing of bytes objects is inefficient. Use a memoryview() instead (`memoryview-over-bytes`)
    W8205 : Importing the "%s" name directly is more efficient in this loop. (`dotted-import-in-loop`)
    W8301 : Use tuple instead of list for a non-mutated sequence. (`use-tuple-over-list`)
    W8401 : Use a list comprehension instead of a for-loop (`use-list-comprehension`)
    W8402 : Use a list copy instead of a for-loop (`use-list-copy`)
    W8403 : Use a dictionary comprehension instead of a for-loop (`use-dict-comprehension`)
  3. Run perflint as a standalone linter

    main

    You can run perflint directly from the command line by passing the directory or file you wish to lint.

    perflint your_code/
  4. Use perflint as a Pylint plugin

    main

    To integrate perflint into your existing pylint workflow, use the --load-plugins flag.

    pylint your_code/ --load-plugins=perflint
  5. W8301: Use tuple instead of list for a non-mutated sequence

    main

    The ListChecker identifies instances where a list is used for a sequence that is never mutated within its scope. In such cases, using a tuple is more efficient.

    This rule triggers when a list is assigned and no subsequent mutation methods (like those that modify the list in-place) are called on that list within the same module or function scope.

    W8301: Use tuple instead of list for a non-mutated sequence (use-tuple-over-list)
  6. ForLoopChecker: Detect inefficient for-loop usage

    main

    The ForLoopChecker class identifies performance anti-patterns specifically related to how iterables are used in for loops. It detects unnecessary type casting and incorrect dictionary iteration methods.

    Detected Messages

    • W8101 (unnecessary-list-cast): Triggered when list() is called on an object that is already an iterable (like a tuple, list, or set). This is inefficient due to eager iteration.
    • W8102 (incorrect-dictionary-iterator): Triggered when .items() is used on a dictionary but the key or value is being ignored using an underscore _. For example, using .items() when you only need values should be replaced with .values().
    msgs = {
        "W8101": (
            "Unnecessary using of list() on an already iterable type.",
            "unnecessary-list-cast",
            "Eager iteration of an iterable is inefficient.",
        ),
        "W8102": (
            "Incorrect iterator method for dictionary, use %s.",
            "incorrect-dictionary-iterator",
            "Incorrect use of .items() when not unpacking key and value.",
        ),
    }
  7. LoopInvariantChecker: Detect performance issues in loop bodies

    main

    The LoopInvariantChecker class identifies various performance anti-patterns within for and while loop bodies. It focuses on expressions that do not change during loop execution or operations that incur high overhead when repeated.

    Detected Messages

    • W8201 (loop-invariant-statement): An expression inside the loop does not depend on any variables that change during the loop. This should be moved outside the loop.
    • W8202 (loop-global-usage): Accessing global names inside a loop is slower than accessing local names. Copy the global to a local variable before the loop.
    • R8203 (loop-try-except-usage): try..except blocks have overhead. Avoid them inside loops unless used for control flow (Note: applies to Python < 3.11).
    • W8204 (memoryview-over-bytes): Slicing bytes objects inside a loop is inefficient. Use memoryview() instead.
    • W8205 (dotted-import-in-loop): Accessing dotted global names (e.g., module.attribute) inside a loop is inefficient. Import the name directly before the loop.
    msgs = {
        "W8201": (
            "Consider moving this expression outside of the loop.",
            "loop-invariant-statement",
            "None of the variables referred to in this expression change within the loop.",
        ),
        "W8202": (
            "Lookups of global names within a loop is inefficient, copy to a local variable outside of the loop first.",
            "loop-global-usage",
            "Global name lookups in Python are slower than local names.",
        ),
        "R8203": (
            "Try..except blocks have an overhead. Avoid using them inside a loop unless you're using them for control-flow. Rule only applies to Python < 3.11.",
            "loop-try-except-usage",
            "Avoid using try..except within a loop.",
        ),
        "W8204": (
            "Looped slicing of bytes objects is inefficient. Use a memoryview() instead",
            "memoryview-over-bytes",
            "Avoid using byte slicing in loops.",
        ),
        "W8205": (
            'Importing the "%s" name directly is more efficient in this loop.',
            "dotted-import-in-loop",
            "Dotted global names in loops are inefficient.",
        ),
    }
  8. Register perflint as a Pylint plugin

    main

    To use perflint as a Pylint plugin, you must provide a register function that accepts a PyLinter instance. The perflint package provides this entrypoint, which automatically registers the following checkers:

    • ForLoopChecker
    • LoopInvariantChecker
    • ListChecker
    • ComprehensionChecker
    def register(linter: "PyLinter") -> None:
        linter.register_checker(ForLoopChecker(linter))
        linter.register_checker(LoopInvariantChecker(linter))
        linter.register_checker(ListChecker(linter))
        linter.register_checker(ComprehensionChecker(linter))
  9. ComprehensionChecker messages

    main

    The ComprehensionChecker emits the following messages when it detects inefficient loop patterns:

    • W8401 (use-list-comprehension): Suggests using a list comprehension instead of a for loop that performs an append or insert operation.
    • W8402 (use-list-copy): Suggests using a list copy (e.g., list(iterable)) instead of a for loop that performs an append or insert operation.
    • W8403 (use-dict-comprehension): Suggests using a dictionary comprehension instead of a for loop that populates a dictionary via subscript assignment.
    msgs = {
        "W8401": (
            "Use a list comprehension instead of a for-loop",
            "use-list-comprehension",
            "",
        ),
        "W8402": (
            "Use a list copy instead of a for-loop",
            "use-list-copy",
            "",
        ),
        "W8403": (
            "Use a dictionary comprehension instead of a for-loop",
            "use-dict-comprehension",
            "",
        ),
    }
  10. W8205: Avoid dotted imports in loops

    main

    Accessing submodules or functions via dotted attributes (e.g., os.path.exists) inside a loop is inefficient because it requires multiple attribute lookups. Import the specific function directly (e.g., from os.path import exists) to speed up execution.

    def even_worse_dotted_import(items):
        for item in items:
            val = os.path.exists(item) # Use `from os.path import exists` instead
  11. W8204: Use `memoryview()` for slicing bytes objects

    main

    Slicing bytes objects creates a copy of the data. For efficient, zero-copy interactions when slicing in a loop, convert the bytes object to a memoryview first.

    def memoryview_slice():
        """Convert to a memoryview first."""
        word = memoryview(b'A' * 1000)
        for i in range(1000):
            n = word[0:i]