• Stars
    star
    582
  • Rank 76,801 (Top 2 %)
  • Language
    Python
  • License
    MIT License
  • Created almost 3 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Python Linter for performance anti patterns

perflint

PyPI PyPI - Downloads

A Linter for performance anti-patterns

This project is an early beta. It will likely raise many false-positives in your code.

Installation

pip install perflint

Usage

Perflint can be used as a standalone linter:

perflint your_code/

Or as a pylint linter plugin:

pylint your_code/ --load-plugins=perflint

VS Code

Add these configuration properties to your .vscode/settings.json file (create if it doesn't exist):

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

Rules

W8101 : Unnecessary list() on already iterable type (unnecessary-list-cast)

Using a list() call to eagerly iterate over an already iterable type is inefficient as a second list iterator is created, after first iterating the value:

def simple_static_tuple():
    """Test warning for casting a tuple to a list."""
    items = (1, 2, 3)
    for i in list(items): # [unnecessary-list-cast]
        print(i)

W8102: Incorrect iterator method for dictionary (incorrect-dictionary-iterator)

Python dictionaries store keys and values in two separate tables. They can be individually iterated. Using .items() and discarding either the key or the value using _ is inefficient, when .keys() or .values() can be used instead:

def simple_dict_keys():
    """Check that dictionary .items() is being used correctly. """
    fruit = {
        'a': 'Apple',
        'b': 'Banana',
    }

    for _, value in fruit.items(): # [incorrect-dictionary-iterator]
        print(value)

    for key, _ in fruit.items(): # [incorrect-dictionary-iterator]
        print(key)

W8201: Loop invariant statement (loop-invariant-statement)

The body of loops will be inspected to determine statements, or expressions where the result is constant (invariant) for each iteration of a loop. This is based on named variables which are not modified during each iteration.

For example:

def loop_invariant_statement():
    """Catch basic loop-invariant function call."""
    x = (1,2,3,4)

    for i in range(10_000):
        # x is never changed in this loop scope,
        # so this expression should be evaluated outside
        print(len(x) * i)  # [loop-invariant-statement]
        #     ^^^^^^ 

len(x) should be evaluated outside the loop since x is not modified within the loop.

def loop_invariant_statement():
    """Catch basic loop-invariant function call."""
    x = (1,2,3,4)
    n = len(x)
    for i in range(10_000):
        print(n * i)  # [loop-invariant-statement]

The loop-invariance checker will underline expressions and sub-expressions within the body using the same rules:

def loop_invariant_statement_more_complex():
    """Catch basic loop-invariant function call."""
    x = [1,2,3,4]
    i = 6

    for j in range(10_000):
        # x is never changed in this loop scope,
        # so this expression should be evaluated outside
        print(len(x) * i + j)
#             ^^^^^^^^^^    [loop-invariant-statement]

Methods are blindly considered side-effects, so if a method is called on a variable, it is assumed to have possibly changed in value and therefore not loop-invariant:

def loop_invariant_statement_method_side_effect():
    """Catch basic loop-invariant function call."""
    x = [1,2,3,4] 
    i = 6

    for j in range(10_000):
        print(len(x) * i + j)
        x.clear()  # x changes as a side-effect

The loop-invariant analysis will walk up the AST until it gets to the whole loop body, so an entire branch could be marked. For example, the expression len(x) > 2 is invariant and therefore should be outside the loop. Also, because x * i is invariant, that statement should also be outside the loop, therefore the entire branch will be marked:

def loop_invariant_branching():
    """Ensure node is walked up to find a loop-invariant branch"""
    x = [1,2,3,4]
    i = 6

    for j in range(10_000):
        # Marks entire branch
        if len(x) > 2:
            print(x * i)

Notes on loop invariance

Functions can have side-effects (print is a good example), so the loop-invariant scanner may give some false-positives.

It will also highlight dotted expressions, e.g. attribute lookups. This may seem noisy, but in some cases this is valid, e.g.

from os.path import exists
import os

def dotted_import():
    for _ in range(100_000):
        return os.path.exists('/')

def direct_import():
    for _ in range(100_000):
        return exists('/')

direct_import() is 10-15% faster than dotted_import() because it doesn't need to load the os global, the path attribute and the exists method for each iteration.

W8202: Global name usage in a loop (loop-global-usage)

Loading globals is slower than loading "fast" local variables. The difference is marginal, but when propagated in a loop, there can be a noticeable speed improvement, e.g.:

d = {
    "x": 1234,
    "y": 5678,
}

def dont_copy_dict_key_to_fast():
    for _ in range(100000):
        d["x"] + d["y"]
        d["x"] + d["y"]
        d["x"] + d["y"]
        d["x"] + d["y"]
        d["x"] + d["y"]

def copy_dict_key_to_fast():
    i = d["x"]
    j = d["y"]

    for _ in range(100000):
        i + j
        i + j
        i + j
        i + j
        i + j

copy_dict_key_to_fast() executes 65% faster than dont_copy_dict_key_to_fast()

R8203 : Try..except blocks have a significant overhead. Avoid using them inside a loop (loop-try-except-usage).

Up to Python 3.10, try...except blocks are computationally expensive compared with if statements.

Avoid using them in a loop as they can cause significant overheads. Refactor your code to not require iteration specific details and put the entire loop in the body of a try block.

W8204 : Looped slicing of bytes objects is inefficient. Use a memoryview() instead (memoryview-over-bytes)

Slicing of bytes is slow as it creates a copy of the data within the requested window. Python has a builtin type, memoryview for zero-copy interactions:

def bytes_slice():
    """Slice using normal bytes"""
    word = b'A' * 1000
    for i in range(1000):
        n = word[0:i]
        #   ^^^^^^^^^ memoryview-over-bytes

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

memoryview_slice() is 30-40% faster than bytes_slice()

W8205 : Importing the "%s" name directly is more efficient in this loop. (dotted-import-in-loop)

In Python you can import a module and then access submodules as attributes. You can also access functions as attributes of that module. This keeps your import statements minimal, however, if you use this method in a loop it is inefficient because each loop iteration it will load global, load attribute and then load method. Because the name isn't an object, "load method" falls back to load attribute via a slow internal path.

Importing the desired function directly is 10-15% faster:

import os  # NOQA

def test_dotted_import(items):
    for item in items:
        val = os.environ[item]  # Use `from os import environ`

def even_worse_dotted_import(items):
    for item in items:
        val = os.path.exists(item) # Use `from os.path import exists` instead

W8301 : Use tuple instead of list for a non-mutated sequence. (use-tuple-over-list)

Constructing a tuple is faster than a list and indexing tuples is faster. When the sequence is not mutated, then a tuple should be used instead:

def index_mutated_list():
    fruit = ["banana", "pear", "orange"]
    fruit[2] = "mandarin"
    len(fruit)
    for i in fruit:
        print(i)

def index_non_mutated_list():
    fruit = ["banana", "pear", "orange"]  # Raises [use-tuple-over-list]
    print(fruit[2])
    len(fruit)
    for i in fruit:
        print(i)

Mutation is determined by subscript assignment, slice assignment, or methods called on the list.

W8401 : Use a list comprehension instead of a for-loop (use-list-comprehension)

List comprehensions are 25% more efficient at creating new lists, with or without an if-statement:

def should_be_a_list_comprehension_filtered():
    """A List comprehension would be more efficient."""
    original = range(10_000)
    filtered = []
    for i in original:
        if i % 2:
            filtered.append(i)

W8402 : Use a list copy instead of a for-loop (use-list-copy)

Use either the list() constructor or list.copy() to copy a list, not another for loop:

def should_be_a_list_copy():
    """Using the copy() method would be more efficient."""
    original = range(10_000)
    filtered = []
    for i in original:
        filtered.append(i)

W8403 : Use a dictionary comprehension instead of a for-loop (use-dict-comprehension)

Dictionary comprehensions should be used in simple loops to construct dictionaries:

def should_be_a_dict_comprehension():
    pairs = (("a", 1), ("b", 2))
    result = {}
    for x, y in pairs:
        result[x] = y

def should_be_a_dict_comprehension_filtered():
    pairs = (("a", 1), ("b", 2))
    result = {}
    for x, y in pairs:
        if y % 2:
            result[x] = y

More Repositories

1

vscode-pets

Adds playful pets 🦀🐱🐶 in your VS Code window
TypeScript
1,553
star
2

wily

A Python application for tracking, reporting on timing and complexity in Python code
Python
1,205
star
3

mocker

A Docker-type runtime, written in 100% Python
Python
794
star
4

pycharm-security

Finds security holes in your Python projects from PyCharm and GitHub
Kotlin
312
star
5

cpython-book-samples

Sample scripts and examples for my CPython Internals book
Python
180
star
6

anti-patterns

Python
137
star
7

instaviz

Instant visualization of Python AST and Code Objects
JavaScript
107
star
8

rich-bench

A little benchmarking tool for Python
Python
100
star
9

retox

For running a local continuous testing environment with tox
Python
76
star
10

ants-azure-demos

Collection of PoCs and Azure Demos
Python
49
star
11

python-assembly-poc

Python
46
star
12

requests-staticmock

A test utility for mocking out requests host from a fixtures directory
Python
42
star
13

django-xss-fuzzer

An XSS fuzzer for Django
Python
38
star
14

workday

A Python client for workday.com
Python
37
star
15

CSnakes

C#
37
star
16

azure-pipelines-python-examples

Example configurations for Azure Build Pipelines for Python
Python
36
star
17

hathi

A dictionary attack tool for PostgreSQL and MSSQL
Python
33
star
18

pep-explorer

An easy to use online explorer for Python Enhancement Proposals
HTML
33
star
19

notations

Estimating Big-O notations for a given function in Python
Python
24
star
20

django-on-azure

Resources for my Django on Azure workshop at PyCon US 2021
CSS
19
star
21

ServiceNowHackathon2016

Sydney Hackathon with ServiceNow March 2016- Slack and ServiceNow
JavaScript
19
star
22

tonybaloney.github.io

HTML
19
star
23

wntf

An anti-recommendation algorithm for twitter
Python
17
star
24

pyucwa

Python client for the Skype for Business (Lync) UCWA 2.0 API
Python
17
star
25

django-on-azure-demo

Tutorial for running Django on Azure
Python
16
star
26

dependabot-bot

Python
15
star
27

sciencelogic

A ScienceLogic EM7 API client for Python
Python
15
star
28

readysalted

An Internet of Things toolkit for SaltStack
C++
14
star
29

hubot-spark

A hubot integration for Cisco Spark
CoffeeScript
14
star
30

dissy

A TUI disassembler
Python
14
star
31

pyinline

Python
13
star
32

St2Client

A StackStorm API client for C#.NET including a PowerShell module
C#
13
star
33

python-railroads

A script to generate railroad diagrams for Python grammar
Python
13
star
34

tonybaloney

12
star
35

netimages

tool for sniffing images over HTTP traffic and showing them on the console. Designed for remote shells.
Python
12
star
36

bad-security-practices

Python
11
star
37

performance_testing

Results from a performance test for Python runtimes
HTML
9
star
38

try-pyjion

JavaScript
8
star
39

confluence-to-powerpoint

Python
8
star
40

generic_demos

Python
7
star
41

python-3.11-demos

Python
6
star
42

libcloud.extra

Some extra packs and examples for libcloud integration with StackStorm
Python
6
star
43

python-task-provider

TypeScript
5
star
44

pluralsight

Python
5
star
45

markdownlint-rule-titlecase

Custom title case rule for markdownlint headers
JavaScript
4
star
46

wily-pycharm

Code Complexity Plugin for PyCharm and IntelliJ-based IDEs
Kotlin
4
star
47

multicloud

Python
4
star
48

pytest-freethreaded

Python
4
star
49

pyrower

A Python Rowing Machine
Python
3
star
50

django-cosmos

A Cosmos DB driver for Django
Python
3
star
51

vs-test-detail

An NUnit extension for Azure DevOps
TypeScript
3
star
52

Cloud-auto-scaling

Provides auto-scaling capabilities to a group of Virtual Servers via SNMP, currently works with Abiquo
PHP
3
star
53

python-for-csharp-java-devs

2
star
54

pywinexe

Python bindings for winexe
Python
2
star
55

rightscale-agent-libcloud

Python
2
star
56

pathgather

Python client for Pathgather API
Python
2
star
57

pyjion-home

The website for www.trypyjion.com
CSS
2
star
58

tox-nuitka

A tox plugin for executing via nuitka
Python
2
star
59

cpython-clion-demo

Demo of debugging CPython from CLion
C
2
star
60

simple-flask-azd

A tiny template for Azure Developer CLI with Flask running in App Service
Bicep
1
star
61

email-toolbox

Python
1
star
62

panic_room

Python
1
star
63

pysaba

Python
1
star
64

cisco-spark-async-bot

A Python Asynchronous Bot for Cisco Spark API
Python
1
star
65

hubot-servicenow

JavaScript
1
star
66

ants-vscode-session

Jupyter Notebook
1
star
67

Abiquo-Backup

Backup Virtual Machines in an Abiquo Cloud
PHP
1
star
68

cognitive-kitchen-sink

SCSS
1
star
69

No-More-Spreadsheets

A web based service catalogue and pricing tool for products and services.
ASP
1
star
70

PHPVarnish

PHP Class for Varnish Management CLI
PHP
1
star
71

pycharm-webinar

Demo project for PyCharm webinar
Python
1
star
72

pyupgradesim

CSS
1
star