关于python:如果计算将被大量使用,我应该将它存储在变量中吗?

Should I store a calculation in a variable if it will be used a lot?

例如,如果我有一个函数来检查list1是否是list2的子列表,哪个选项更好:

选项1:

1
2
3
4
5
6
7
8
9
def isSublist1(list1,list2):
 "This fuction checks if list1 is a sublist of list2."
  for i in range(len(list2)):
    part=list2[i:] # part is a list with all the elements from i to the end of list2
    if len(part)<len(list1):
      return False
    if list1==part[:len(list1)]: # if list1 is in the beginning of part
      return True
  return False

或选项2:

1
2
3
4
5
6
7
8
def isSublist2(list1,list2):
 "This fuction checks if list1 is a sublist of list."
  for i in range(len(list2)):
    if len(list2[i:])<len(list1):
      return False
    if list1==list2[i:][:len(list1)]: # if list1 is in the beginning of list2[i:] (part)
      return True
  return False

在选项1中,我使用一个名为part的变量来存储list2的一个部分,但是在选项2中,part不是一个变量,list2的部分是在需要时计算出来的。选项1是否更快?它会占用更多的空间吗?

我的问题不在于这个函数的特殊性,我知道还有其他方法可以实现这个函数。

我想知道在一个循环中哪一个是最佳实践:使用一个变量来避免计算相同事物的几倍。答案是否取决于计算的复杂性和频率?


补充帕特里克的优秀答案,让我们用您的实际代码来尝试计时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> def isSublist1(list1,list2):                                                                                                                                                                                                                              
...   for i in range(len(list2)):                                                                                                                                                                                                                            
...     part=list2[i:]                                                                                                                                                                                                                                        
...     if len(part)<len(list1):                                                                                                                                                                                                                              
...       return False                                                                                                                                                                                                                                        
...     if list1==part[:len(list1)]:
...       return True                                                                                                                                                                                                                                        
...   return False                                                                                                                                                                                                                                            
...
>>> def isSublist2(list1,list2):
...   for i in range(len(list2)):
...     if len(list2[i:])<len(list1):
...       return False
...     if list1==list2[i:][:len(list1)]:
...       return True
...   return False
...
>>> list1=list(range(10000))
>>> list2=list(range(4000,4020))
>>> import timeit
>>> timeit.timeit('isSublist1(list2,list1)', globals=globals(),number=100)
6.420147094002459
>>> timeit.timeit('isSublist2(list2,list1)', globals=globals(),number=100)
12.455138996010646

所以在我的系统中,使用临时变量所需的时间大约是不使用临时变量所需时间的一半。

我不知道您的列表和子列表的性质;您可能希望更改list1和list2是如何使用代码的良好反映,但至少在我看来,保存临时变量是一个非常好的主意。

顺便说一下,让我们做另一个有趣的实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def isSublist3(list1,list2):
...   ln = len(list1)
...   for i in range(len(list2)):
...     part=list2[i:]
...     if len(part)<ln:
...       return False
...     if list1==part[:ln]:
...       return True
...   return False
...
>>> timeit.timeit('isSublist1(list2,list1)',globals=globals(),number=100); timeit.timeit('isSublist3(list2,list1)',globals=globals(),number=100)
6.549526696035173
6.481004184985068

我又跑了几次,看看能得到什么:

6.4708752420265236.463623657007702

6.151073662971612年5.787795798969455号

5.6856079949648125.655005165026523

6.439315696140916.372227535001002

请注意,每次缓存的长度比非缓存的长度花费的时间要少,尽管缓存切片并不能提高性能。

还要注意,不要从一次时间运行中得出太多的结论,这一点很重要。有很多其他的变量影响了时间(在我的例子中,很明显,发生了一些事情,使它从6.4下降到5.7——在一个测试的中间!)所以,如果你想想出一个好的规则,你可以依靠,测试几次,以确保你得到一致的结果。


存储本地更好,因为python的查找速度更快。在本地存储函数甚至是有回报的。

绩效问题最好通过计时来回答-您可以使用时间IT来衡量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import timeit

def noTempFunc():
    for _ in range(200):
        max([1,4,5,6])

def tempFunc():
    m = max
    for _ in range(200):
        m([1,4,5,6])


print(timeit.timeit(noTempFunc, number=1000))   #  0.055301458000030834
print(timeit.timeit(tempFunc, number=1000))     #  0.049811941999905684 : 11% faster

在这种情况下,只需查找一次全局上下文的max(),然后在本地进行进一步的查找,根据这些数字,查找速度大约快11%。

如果你多次使用你当地的m(),就可以得到回报。

在您的情况下,缓存len_list1 = len(list1)是明智的,因为它使用了很多时间,而且不会改变。

为了使其更具可读性,您可以考虑:

1
2
3
4
5
6
7
8
9
10
11
def isSublist(list1, list2):
   """Checks if list2 is a sublist of list1"""
    len_part = len(list2)  # reused inside the list comp, only"calulated" once
    return any( x == list2 for x in (list1[i:i+len_part]
                                     for i in range(len(list1)-len_part+1) ))


print(isSublist([1,2,3,4],[1]))
print(isSublist([1,2,3,4],[2,3]))
print(isSublist([1,2,3,4],[1,2,4]))
print(isSublist([1,2,3,4],[1,2,3,4]))

输出:

1
2
3
4
True
True
False
True

查找:

  • any()函数的工作原理是什么?
  • 将python列表拆分为其他"子列表",即较小的列表

您的缓存长度更快的版本(基于Scott Mermelstein的回答):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def isSublist1a(list1,list2): # cached length as well
    l1 = len(list1)
    for i in range(len(list2)):
        part=list2[i:]
        if len(part)<l1:
            return False
        if list1==part[:l1]:
            return True
    return False

list1=list(range(1000))
list2=list(range(400,420))
import timeit
print(timeit.timeit('isSublist1(list2,list1)', globals=globals(),number=1000))
print(timeit.timeit('isSublist1a(list2,list1)', globals=globals(),number=1000))
print(timeit.timeit('isSublist2(list2,list1)', globals=globals(),number=1000))

交付(2次执行):

1
2
3
4
5
6
7
0.08652938600062043  # cached part
0.08017484299944044  # cached part + cached list1 lenght - slightly faster
0.15090413599955355  # non-cached version

0.8882850420004615   # cached part
0.8294611960000111   # cached part + cached list1 lenght - slightly faster
1.5524438030006422   # non-cached version