Install perflint via pip
mainInstall the perflint linter using pip to begin checking your Python code for performance anti-patterns.
pip install perflintrepository·main·Indexed 20 days ago
https://github.com/tonybaloney/perflintA 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.
Install the perflint linter using pip to begin checking your Python code for performance anti-patterns.
pip install perflintTo 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"
],
}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`)You can run perflint directly from the command line by passing the directory or file you wish to lint.
perflint your_code/To integrate perflint into your existing pylint workflow, use the --load-plugins flag.
pylint your_code/ --load-plugins=perflintThe 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)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.
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.",
),
}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.
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.",
),
}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:
ForLoopCheckerLoopInvariantCheckerListCheckerComprehensionCheckerdef register(linter: "PyLinter") -> None:
linter.register_checker(ForLoopChecker(linter))
linter.register_checker(LoopInvariantChecker(linter))
linter.register_checker(ListChecker(linter))
linter.register_checker(ComprehensionChecker(linter))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",
"",
),
}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` insteadSlicing 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]