具有字典理解的数据过滤性能

Data filtering performance with dict comprehension

在寻找最有效的字典过滤方法时,我偶然发现了一个奇怪的行为。

我做了 4 个测试,第一个按顺序过滤字典。

第二种结合规则进行一次过滤(实际上是最有效的方式)。

然后,我尝试使其更通用,以便过滤器可以与任意数量的谓词一起使用,最终可以由用户定义,而不是硬编码。

而且我意识到将 predicated 与 all 相结合比一个接一个地进行两次过滤效率低得多。

有什么可以解释的?这是性能不佳的 all() 函数吗? 您是否会建议任何其他方法来以通用方式提高性能?

# TEST 1 (Took 1.717677354812622s)
y = {k:x for k,x in y.items() if x['id'] >= 3 }
y = {k:x for k,x in y.items() if x['name'].find('a') != -1 }


# TEST 2 (Took 1.411365032196045s)
y = {k:x for k,x in y.items() if x['id'] >= 3 and x['name'].find('a') != -1  }

# TEST 3 (Took 3.4738941192626953s)
predicates = [
    lambda x: x['id'] >= 3,
    lambda x: x['name'].find('a') != -1
]
y = {k:x for k,x in y.items() if all([f(x) for f in predicates]) }

# TEST 4 (Took 2.4156315326690674s)
predicates = [
    lambda x: x['id'] >= 3,
]
y = {k:x for k,x in y.items() if predicates[0](x) }
predicates = [
    lambda x: x['name'].find('a') != -1
]
y = {k:x for k,x in y.items() if predicates[0](x) }

测试台:

from var_dump import var_dump
import time

start_time = time.time()

for p in range(0,1000000):
    users = {
        1: {'id': 1, 'name': "toto"},
        2: {'id': 2, 'name': "titi"},
        3: {'id': 3, 'name': "tata"},
        4: {'id': 4, 'name': "tutu"},
        5: {'id': 5, 'name': "john"},
        6: {'id': 6, 'name': "jane"}
    }

    y = users

    #-> test goes here

print(y)
print("--- %s seconds ---" % (time.time() - start_time))

我认为这是由于使用了生成器表达式,正如这个问题中所回答的那样

all 评估两个条件,而 TEST_2 中的 and 被延迟评估,这意味着第二个条件仅针对 ids >= 3 进行评估。 通过简单的打印语句进行说明:

users = {
    1: {'id': 1, 'name': "toto"},
    2: {'id': 2, 'name': "titi"},
    3: {'id': 3, 'name': "tata"},
    4: {'id': 4, 'name': "tutu"},
    5: {'id': 5, 'name': "john"},
    6: {'id': 6, 'name': "jane"}
}

def check_1(item):
    print("check_1")
    return item['id'] >= 3

def check_2(item):
    print("check_2")
    return item['name'].find('a') != -1


# all condition
print("TEST AND")
for k, v in users.items():
    check_1(v) and check_2(v)

# all condition
print("TEST ALL")
for k, v in users.items():
    all([check_1(v), check_2(v)])

输出:

TEST AND
check_1
check_1
check_1
check_1
check_2
check_1
check_2
check_1
check_2
TEST ALL
check_1
check_2
check_1
check_2
check_1
check_2
check_1
check_2
check_1
check_2
check_1
check_2

因为您在括号中评估谓词,所以会创建一个列表,并且将检查所有项目的所有条件:all([f(x) for f in predicates])。这意味着与其他方法相比,您正在为更多项目测试第二个谓词,并最终将苹果与橙子进行比较。

all() 函数可以使用生成器,在这种情况下,只要其中一个条件为假,它就会停止,从而使评估短路。删除括号:all(f(x) for f in predicates) 并且您将比较橙子和橙子。

请注意,x['name'].find('a') != -1 可以替换为 'a' in x['name'],恕我直言,这样会更清楚