{"id":13468886,"url":"https://github.com/tonybaloney/perflint","last_synced_at":"2025-05-15T20:03:33.120Z","repository":{"id":37330949,"uuid":"445408927","full_name":"tonybaloney/perflint","owner":"tonybaloney","description":"Python Linter for performance anti patterns","archived":false,"fork":false,"pushed_at":"2024-02-19T11:47:03.000Z","size":152,"stargazers_count":687,"open_issues_count":22,"forks_count":10,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-05-10T09:22:35.948Z","etag":null,"topics":["hacktoberfest","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonybaloney.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-07T05:29:29.000Z","updated_at":"2025-05-08T05:55:40.000Z","dependencies_parsed_at":"2024-01-13T16:25:55.520Z","dependency_job_id":"32d9e3d9-4b84-4e7a-9a1d-d5979bbd5121","html_url":"https://github.com/tonybaloney/perflint","commit_stats":{"total_commits":69,"total_committers":4,"mean_commits":17.25,"dds":0.04347826086956519,"last_synced_commit":"c07391c17671c3c9d5a7fd69120d1f570e268d58"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonybaloney%2Fperflint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonybaloney%2Fperflint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonybaloney%2Fperflint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonybaloney%2Fperflint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonybaloney","download_url":"https://codeload.github.com/tonybaloney/perflint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414493,"owners_count":22067271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","python"],"created_at":"2024-07-31T15:01:21.266Z","updated_at":"2025-05-15T20:03:29.935Z","avatar_url":"https://github.com/tonybaloney.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# perflint\n\n[![PyPI](https://img.shields.io/pypi/v/perflint)](https://pypi.org/project/perflint/)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/perflint)](https://pypi.org/project/perflint/)\n\nA Linter for performance anti-patterns\n\nThis project is an early beta. It will likely raise many false-positives in your code.\n\n## Installation\n\n```console\npip install perflint\n```\n\n## Usage\n\nPerflint can be used as a standalone linter:\n\n```console\nperflint your_code/\n```\n\nOr as a `pylint` linter plugin:\n\n```console\npylint your_code/ --load-plugins=perflint\n```\n\n### VS Code\n\nAdd these configuration properties to your `.vscode/settings.json` file (create if it doesn't exist):\n\n```javascript\n{\n    \"python.linting.pylintEnabled\": true,\n    \"python.linting.enabled\": true,\n    \"python.linting.pylintArgs\": [\n        \"--load-plugins\",\n        \"perflint\",\n        \"--rcfile\",\n        \"${workspaceFolder}/.pylintrc\"\n    ],\n}\n```\n\n## Rules\n\n### W8101 : Unnecessary `list()` on already iterable type (`unnecessary-list-cast`)\n\nUsing 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:\n\n```python\ndef simple_static_tuple():\n    \"\"\"Test warning for casting a tuple to a list.\"\"\"\n    items = (1, 2, 3)\n    for i in list(items): # [unnecessary-list-cast]\n        print(i)\n```\n\n### W8102: Incorrect iterator method for dictionary (`incorrect-dictionary-iterator`)\n\nPython 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:\n\n```python\ndef simple_dict_keys():\n    \"\"\"Check that dictionary .items() is being used correctly. \"\"\"\n    fruit = {\n        'a': 'Apple',\n        'b': 'Banana',\n    }\n\n    for _, value in fruit.items(): # [incorrect-dictionary-iterator]\n        print(value)\n\n    for key, _ in fruit.items(): # [incorrect-dictionary-iterator]\n        print(key)\n```\n\n### W8201: Loop invariant statement (`loop-invariant-statement`)\n\nThe 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.\n\nFor example:\n\n```python\ndef loop_invariant_statement():\n    \"\"\"Catch basic loop-invariant function call.\"\"\"\n    x = (1,2,3,4)\n\n    for i in range(10_000):\n        # x is never changed in this loop scope,\n        # so this expression should be evaluated outside\n        print(len(x) * i)  # [loop-invariant-statement]\n        #     ^^^^^^ \n```\n\n`len(x)` should be evaluated outside the loop since `x` is not modified within the loop.\n\n```python\ndef loop_invariant_statement():\n    \"\"\"Catch basic loop-invariant function call.\"\"\"\n    x = (1,2,3,4)\n    n = len(x)\n    for i in range(10_000):\n        print(n * i)  # [loop-invariant-statement]\n```\n\nThe loop-invariance checker will underline expressions and sub-expressions within the body using the same rules:\n\n```python\ndef loop_invariant_statement_more_complex():\n    \"\"\"Catch basic loop-invariant function call.\"\"\"\n    x = [1,2,3,4]\n    i = 6\n\n    for j in range(10_000):\n        # x is never changed in this loop scope,\n        # so this expression should be evaluated outside\n        print(len(x) * i + j)\n#             ^^^^^^^^^^    [loop-invariant-statement]\n```\n\nMethods 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:\n\n```python\ndef loop_invariant_statement_method_side_effect():\n    \"\"\"Catch basic loop-invariant function call.\"\"\"\n    x = [1,2,3,4] \n    i = 6\n\n    for j in range(10_000):\n        print(len(x) * i + j)\n        x.clear()  # x changes as a side-effect\n```\n\nThe loop-invariant analysis will walk up the AST until it gets to the whole loop body, so an entire branch could be marked.\nFor example, the expression `len(x) \u003e 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:\n\n```python\ndef loop_invariant_branching():\n    \"\"\"Ensure node is walked up to find a loop-invariant branch\"\"\"\n    x = [1,2,3,4]\n    i = 6\n\n    for j in range(10_000):\n        # Marks entire branch\n        if len(x) \u003e 2:\n            print(x * i)\n```\n\n#### Notes on loop invariance\n\nFunctions can have side-effects (print is a good example), so the loop-invariant scanner may give some false-positives.\n\nIt will also highlight dotted expressions, e.g. attribute lookups. This may seem noisy, but in some cases this is valid, e.g.\n\n```python\nfrom os.path import exists\nimport os\n\ndef dotted_import():\n    for _ in range(100_000):\n        return os.path.exists('/')\n\ndef direct_import():\n    for _ in range(100_000):\n        return exists('/')\n```\n\n`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.\n\n### W8202: Global name usage in a loop (`loop-global-usage`)\n\nLoading 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.:\n\n```python\nd = {\n    \"x\": 1234,\n    \"y\": 5678,\n}\n\ndef dont_copy_dict_key_to_fast():\n    for _ in range(100000):\n        d[\"x\"] + d[\"y\"]\n        d[\"x\"] + d[\"y\"]\n        d[\"x\"] + d[\"y\"]\n        d[\"x\"] + d[\"y\"]\n        d[\"x\"] + d[\"y\"]\n\ndef copy_dict_key_to_fast():\n    i = d[\"x\"]\n    j = d[\"y\"]\n\n    for _ in range(100000):\n        i + j\n        i + j\n        i + j\n        i + j\n        i + j\n```\n\n`copy_dict_key_to_fast()` executes 65% faster than `dont_copy_dict_key_to_fast()`\n\n### R8203 : Try..except blocks have a significant overhead. Avoid using them inside a loop (`loop-try-except-usage`).\n\nUp to Python 3.10, `try...except` blocks are computationally expensive compared with `if` statements.\n\nAvoid 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.\n\n### W8204 : Looped slicing of bytes objects is inefficient. Use a memoryview() instead (`memoryview-over-bytes`)\n\nSlicing 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](https://effectivepython.com/2019/10/22/memoryview-bytearray-zero-copy-interactions):\n\n```python\ndef bytes_slice():\n    \"\"\"Slice using normal bytes\"\"\"\n    word = b'A' * 1000\n    for i in range(1000):\n        n = word[0:i]\n        #   ^^^^^^^^^ memoryview-over-bytes\n\ndef memoryview_slice():\n    \"\"\"Convert to a memoryview first.\"\"\"\n    word = memoryview(b'A' * 1000)\n    for i in range(1000):\n        n = word[0:i]\n\n```\n\n`memoryview_slice()` is 30-40% faster than `bytes_slice()`\n\n### W8205 : Importing the \"%s\" name directly is more efficient in this loop. (`dotted-import-in-loop`)\n\nIn 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.\n\nImporting the desired function directly is 10-15% faster:\n\n```python\nimport os  # NOQA\n\ndef test_dotted_import(items):\n    for item in items:\n        val = os.environ[item]  # Use `from os import environ`\n\ndef even_worse_dotted_import(items):\n    for item in items:\n        val = os.path.exists(item) # Use `from os.path import exists` instead\n```\n\n### W8301 : Use tuple instead of list for a non-mutated sequence. (`use-tuple-over-list`)\n\nConstructing 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:\n\n```python\ndef index_mutated_list():\n    fruit = [\"banana\", \"pear\", \"orange\"]\n    fruit[2] = \"mandarin\"\n    len(fruit)\n    for i in fruit:\n        print(i)\n\ndef index_non_mutated_list():\n    fruit = [\"banana\", \"pear\", \"orange\"]  # Raises [use-tuple-over-list]\n    print(fruit[2])\n    len(fruit)\n    for i in fruit:\n        print(i)\n```\n\nMutation is determined by subscript assignment, slice assignment, or methods called on the list.\n\n### W8401 : Use a list comprehension instead of a for-loop (`use-list-comprehension`)\n\nList comprehensions are 25% more efficient at creating new lists, with or without an if-statement:\n\n```python\ndef should_be_a_list_comprehension_filtered():\n    \"\"\"A List comprehension would be more efficient.\"\"\"\n    original = range(10_000)\n    filtered = []\n    for i in original:\n        if i % 2:\n            filtered.append(i)\n```\n\n### W8402 : Use a list copy instead of a for-loop (`use-list-copy`)\n\nUse either the `list()` constructor or `list.copy()` to copy a list, not another for loop:\n\n```python\ndef should_be_a_list_copy():\n    \"\"\"Using the copy() method would be more efficient.\"\"\"\n    original = range(10_000)\n    filtered = []\n    for i in original:\n        filtered.append(i)\n```\n\n### W8403 : Use a dictionary comprehension instead of a for-loop (`use-dict-comprehension`)\n\nDictionary comprehensions should be used in simple loops to construct dictionaries:\n\n```python\ndef should_be_a_dict_comprehension():\n    pairs = ((\"a\", 1), (\"b\", 2))\n    result = {}\n    for x, y in pairs:\n        result[x] = y\n\ndef should_be_a_dict_comprehension_filtered():\n    pairs = ((\"a\", 1), (\"b\", 2))\n    result = {}\n    for x, y in pairs:\n        if y % 2:\n            result[x] = y\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonybaloney%2Fperflint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonybaloney%2Fperflint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonybaloney%2Fperflint/lists"}