【问题标题】:is it possible to simplify loops and multiple if statement checks for speed?是否可以简化循环和多个 if 语句检查速度?
【发布时间】:2023-03-19 15:40:01
【问题描述】:

我有一个相同 python 对象的几个实例的列表,我想循环并执行 3 个方法;但是,在某些情况下,我想选择运行 3 个中的哪一个(以任意组合)。

我可以这样做:

do_method_1 = True
do_method_2 = True
do_method_3 = True

for item in list_of_items:
    if do_method_1:
        item.method_1()
    if do_method_2:
        item.method_2()
    if do_method_2:
        item.method_2()

这很容易阅读,但是,我对循环中的每个项目都进行了多次检查,并稍微减慢了速度。我也可以通过每次检查来翻转它,然后循环检查每个真/假检查的项目......但我可能会多次循环检查所有内容并以这种方式减慢速度。

我很好奇是否有一种简单的方法可以只评估检查和循环一次。我目前有这样的东西:

do_method_1 = True
do_method_2 = True
do_method_3 = True

if do_method_1:
    if do_method_2:
        if do_method_3:
            for item in list_of_items:
                item.method_1()
                item.method_2()
                item.method_3()
        else:
            for item in list_of_items:
                item.method_1()
                item.method_2()
    elif do_method_3:
        for item in list_of_items:
            item.method_1()
            item.method_3()
    else:
        for item in list_of_items:
            item.method_1()
else:
    if do_method_2:
        if do_method_3:
            for item in list_of_items:
                item.method_2()
                item.method_3()
        else:
            for item in list_of_items:
                item.method_2()
    elif do_method_3:
        for item in list_of_items:
            item.method_3()
    else:
        print('not doing anything...')

这确实可行,因此循环只执行一次,而且我认为从技术上讲,每次检查只会被评估一次,但这是很多重复的代码混乱,如果一个第四种方法已添加到可能性列表中。那么,是否有另一种“更简洁”的方式来写出这个循环,只执行一次循环,并且每次只检查一次以提高速度?

谢谢!

【问题讨论】:

  • 您是否分析过您的代码以确定这是一个瓶颈?
  • 除了@C_Z_ 提出的问题,你能分享一些更接近你实际代码的东西吗?

标签: python


【解决方案1】:

在循环之前,决定每次调用是methodcaller 对象还是无操作函数。

from operator import methodcaller

def noop(obj):
    pass

do_method_1 = True
do_method_2 = True
do_method_3 = True

m1 = methodcaller('method_1') if do_method_1 else noop
m2 = methodcaller('method_2') if do_method_2 else noop
m3 = methodcaller('method_3') if do_method_3 else noop    


for item in list_of_items:
    m1(item)
    m2(item)
    m3(item)

类似于Barmar's answer,您可以创建一个methodcaller 对象列表以使用并对其进行迭代,从而消除调用noop 的成本。

【讨论】:

  • 但请注意,在性能方面它只比 Barmar 的 getattr() 建议快一点,并且比循环中的原始普通 ifs 慢。动态方法调用很昂贵!
  • 这真的很有帮助 - 我最终做了一些与此非常相似的事情......我使用了 Item.method_x (类似于 Alex Skalozub 的答案中的 test3 )而不是方法调用者。我喜欢它没有任何嵌套循环和检查,noop 调用真的不是很受欢迎(如果有的话),非常感谢!
【解决方案2】:

列出要调用的方法,然后循环遍历该列表。

methods = ['method_1', 'method_2', 'method_3']
for item in list_of_items:
    for m in methods:
        getattr(item, m)()

然后你可以更改methods的内容以反映情况。

Call a Python method by name

【讨论】:

  • 另一种选择——不是在这种情况下需要它(或有很大的不同)是允许 Python 执行 getattr 机器:for m in (item.method_1, item.method_2, item.method_3): m()
  • @JonClements 但是条件逻辑在哪里?它需要是他可以通过变量控制的东西。
  • 移动到循环内部,methods 直接包含属性而不是名称...所以大概是相同的方式但是 methods 以某种方式构建...
  • 我仍然不明白你的意思,除非你建议 Alex Skalozub 的方法使用像 [Item.method_1, Item.method_2, ...] 这样的列表。
【解决方案3】:

如果list_of_items 中的项目都具有相同(并且已知)类型,则使用方法列表而不是名称列表将快两倍,并且与使用ifs 的普通循环几乎相同。这可能对大量项目很重要。

import time

class Item:
    def method_1(self):
        pass
    def method_2(self):
        pass
    def method_3(self):
        pass

list_of_items = []  
for i in range(100000):
    list_of_items.append(Item())

do_method_1 = True
do_method_2 = True
do_method_3 = True

def test1():
    for item in list_of_items:
        if do_method_1: item.method_1()
        if do_method_2: item.method_2()
        if do_method_2: item.method_2()

def test2():
    methods = []
    if do_method_1: methods.append('method_1')
    if do_method_2: methods.append('method_2')
    if do_method_3: methods.append('method_3')

    for item in list_of_items:
        for m in methods:
            getattr(item, m)()

def test3():
    methods = []
    if do_method_1: methods.append(Item.method_1)
    if do_method_2: methods.append(Item.method_2)
    if do_method_3: methods.append(Item.method_3)

    for item in list_of_items:
        for m in methods:
            m(item)

print('testing plain ifs')
start = time.time()
test1()
print(time.time() - start)

print('testing method names')
start = time.time()
test2()
print(time.time() - start)

print('testing method ptrs')
start = time.time()
test3()
print(time.time() - start)

输出:

testing plain ifs
0.022666215896606445
testing method names
0.04316902160644531
testing method ptrs
0.021532058715820312

请注意,这仅在所有 3 个方法具有相同数量的参数时才有效。一旦参数不同,使用ifs 的初始实现就是要走的路。

【讨论】:

  • 感谢您查看这些不同的选项以及时间测试 - 它有助于找到我正在寻找的解决方案!
猜你喜欢
  • 2016-08-08
  • 1970-01-01
  • 2022-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 2022-11-17
相关资源
最近更新 更多