跳转到主要内容

性能反模式Pylint扩展

项目描述

perflint

PyPI PyPI - Downloads

性能反模式的检查器

此项目是一个早期测试版。它可能会在您的代码中引发许多误报。

安装

pip install perflint

使用

Perflint可以用作独立的检查器

perflint your_code/

或作为pylint检查器插件

pylint your_code/ --load-plugins=perflint

VS Code

将这些配置属性添加到您的.vscode/settings.json文件中(如果不存在则创建)

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

规则

W8101 : 对已可迭代的类型进行不必要的list()操作(unnecessary-list-cast

使用一个list()调用对已可迭代的类型进行贪婪迭代是不高效的,因为它在迭代值之后创建了第二个列表迭代器

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-dictionary-iterator

Python字典在两个不同的表中存储键和值。它们可以分别迭代。当可以使用.keys().values()时,使用.items()并丢弃其中一个键或值使用_是不高效的

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

将检查循环体以确定语句或表达式,其中结果对于每次循环迭代都是常数(不变)的。这是基于在每次迭代期间未修改的命名变量。

例如

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)应该在循环外部评估,因为x在循环内没有修改。

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]

循环不变性检查器将使用相同的规则在循环体内部和子表达式中下划线表达式

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]

方法被认为是盲目的副作用,因此如果对一个变量调用方法,则假定它的值可能已更改,因此不是循环不变的

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

循环不变性分析会遍历AST直到整个循环体,因此可以标记整个分支。例如,表达式 len(x) > 2 是不变的,因此应该放在循环外。同样,由于 x * i 是不变的,因此该语句也应该放在循环外,因此整个分支将被标记。

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)

循环不变性注意事项

函数可以有副作用(print 是一个很好的例子),因此循环不变性扫描程序可能会给出一些误报。

它还会突出显示点表达式,例如属性查找。这可能看起来很嘈杂,但在某些情况下这是有效的,例如。

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() 比比 dotted_import() 快 10-15%,因为它不需要在每次迭代中加载 os 全局、path 属性和 exists 方法。

W8202:循环中的全局名使用(《循环全局使用》)

加载全局变量比加载“快速”局部变量要慢。差异很小,但当你在一个循环中传播时,可能会有明显的速度提升,例如。

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()dont_copy_dict_key_to_fast() 执行快 65%

R8203:try...except 块有显著的开销。请避免在循环内使用它们(《循环 try-except 使用》)。

直到 Python 3.10,与 if 语句相比,try...except 块在计算上更昂贵。

请避免在循环中使用它们,因为它们可能会引起显著的开销。重构您的代码,使其不需要迭代特定细节,并将整个循环放在 try 块的体中。

W8204:对字节对象的循环切片效率低下。请使用 memoryview()(《memoryview-over-bytes》)

切片 bytes 很慢,因为它会在请求的窗口内创建数据的副本。Python 有一个内置类型 memoryview 用于 零拷贝交互

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()bytes_slice() 快 30-40%

W8205:直接导入 "%s" 名称在此循环中更高效。(《dotted-import-in-loop》)

在 Python 中,你可以导入一个模块,然后以属性的方式访问子模块。你还可以以该模块的属性方式访问函数。这使你的导入语句尽可能少,然而,如果你在循环中使用这种方法,它是低效的,因为每次循环迭代它都会加载全局变量、加载属性然后加载方法。因为该名称不是一个对象,"加载方法" 会回退到通过慢速内部路径加载属性。

直接导入所需的函数要快 10-15%

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-over-list》)

构造元组比构造列表要快,而且索引元组也更快。当序列不可变时,则应使用元组。

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)

修改是通过子脚本赋值、切片赋值或对列表调用的方法确定的。

W8401:请使用列表推导式而不是 for 循环(《use-list-comprehension》)

列表推导式在创建新列表(无论是否有 if 语句)时比 for 循环快 25%

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:请使用列表复制而不是 for 循环(《use-list-copy》)

使用 list() 构造函数或 list.copy() 来复制列表,而不是另一个 for 循环。

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:请使用字典推导式而不是 for 循环(《use-dict-comprehension》)

在简单的循环中应使用字典推导式来构建字典。

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

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分发

perflint-0.8.1.tar.gz (26.0 kB 查看哈希值)

上传时间

构建分发

perflint-0.8.1-py3-none-any.whl (11.4 kB 查看哈希值)

上传时间 Python 3

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面