这种观点在关于编程语言的讨论中经常出现,经常掩盖了Python的众多优点。
事实是,如果能以Pythonic的方式编写Python代码,它是很快的。
细节决定成败。经验丰富的Python开发者掌握了一系列微妙而强大的技巧,可以显著提高代码的性能。
这些技巧乍看之下似乎微不足道,但它们可以带来效率的大幅提升。让我们深入了解其中的9种方法,改变编写和优化Python代码的方式。
如果有大量字符串等待处理,字符串连接将成为Python程序的瓶颈。
基本上,Python有两种字符串连接的方式:
那么哪种方式更快呢?
现在,让我们定义3个不同的函数来连接相同的字符串:
mylist = ["Yang", "Zhou", "is", "writing"]# 使用'+'def concat_plus(): result = "" for word in mylist: result += word + " " return result# 使用'join()'def concat_join(): return " ".join(mylist)# 直接连接而不使用列表def concat_directly(): return "Yang" + "Zhou" + "is" + "writing"
根据你的第一印象,你认为哪个函数最快,哪个最慢?
真正的结果可能会让你惊讶:
import timeitprint(timeit.timeit(concat_plus, number=10000))# 0.002738415962085128print(timeit.timeit(concat_join, number=10000))# 0.0008482920238748193print(timeit.timeit(concat_directly, number=10000))# 0.00021425005979835987
如上所示,对于连接一组字符串,join()方法比在for循环中逐个添加字符串更快。
原因很简单。一方面,字符串在Python中是不可变的数据,每次+=操作都会创建一个新字符串并复制旧字符串,这在计算上成本是昂贵的。
另一方面,.join()方法专门针对连接一系列字符串进行了优化。它会预先计算出所生成字符串的大小,然后一次性创建它。因此,它避免了循环中+=操作带来的开销,从而使速度更快。
然而,在我们的测试中,速度最快的函数是直接连接字符串文字。它的高速度是由于:
总之,如果需要连接一组字符串,请选择join()而不是+=。如果想要直接连接字符串,只需使用+即可。
创建列表并不是很难的事情。常见的两种方式是:
让我们使用一个简单的代码片段来测试它们的性能:
import timeitprint(timeit.timeit('[]', number=10 ** 7))# 0.1368238340364769print(timeit.timeit(list, number=10 ** 7))# 0.2958830420393497
结果显示,执行list()函数比直接使用[]要慢。
这是因为[]是一种字面量语法,而list()是一个构造函数调用。调用函数无疑需要额外的时间。
从同样的逻辑出发,在创建字典时,我们也应该使用{}而不是dict()。
成员测试操作的性能在很大程度上依赖于底层数据结构:
import timeitlarge_dataset = range(100000)search_element = 2077large_list = list(large_dataset)large_set = set(large_dataset)def list_membership_test(): return search_element in large_listdef set_membership_test(): return search_element in large_setprint(timeit.timeit(list_membership_test, number=1000))# 0.01112208398990333print(timeit.timeit(set_membership_test, number=1000))# 3.27499583363533e-05
正如上述代码所示,使用集合进行成员测试比使用列表更快。
为什么会这样呢?
这里的关键在于:在编写程序时要仔细考虑底层数据结构。正确利用合适的数据结构可以显著加快代码的运行速度。
Python中有四种推导式类型:列表推导式、字典推导式、集合推导式和生成器推导式。它们不仅为创建相对数据结构提供了更简洁的语法,而且比使用for循环更高效,因为它们在Python的C实现中进行了优化。
import timeitdef generate_squares_for_loop(): squares = [] for i in range(1000): squares.append(i * i) return squaresdef generate_squares_comprehension(): return [i * i for i in range(1000)]print(timeit.timeit(generate_squares_for_loop, number=10000))# 0.2797503340989351print(timeit.timeit(generate_squares_comprehension, number=10000))# 0.2364629579242319
上述代码是列表推导式和for循环之间的简单速度比较。结果显示,列表推导式更快。
在Python中,访问局部变量比访问全局变量或对象的属性更快。
以下是一个实例来证明这一点:
import timeitclass Example: def __init__(self): self.value = 0obj = Example()def test_dot_notation(): for _ in range(1000): obj.value += 1def test_local_variable(): value = obj.value for _ in range(1000): value += 1 obj.value = valueprint(timeit.timeit(test_dot_notation, number=1000))# 0.036605041939765215print(timeit.timeit(test_local_variable, number=1000))# 0.024470250005833805
这就是Python的工作原理。直观地说,当一个函数被编译时,其中的局部变量是已知的,但其他外部变量需要时间来检索。
这可能是一个小问题,但是当处理大量数据时,我们可以利用它来优化我们的代码。
当工程师们说到Python时,默认情况下指的是CPython。因为CPython是Python语言的默认实现,也是使用最广泛的实现。
鉴于它的大部分内置模块和库都是用C语言编写的,而C语言是一种更快且更底层的语言,因此我们应该利用这些内置模块和库,避免重复劳动。
import timeitimport randomfrom collections import Counterdef count_frequency_custom(lst): frequency = {} for item in lst: if item in frequency: frequency[item] += 1 else: frequency[item] = 1 return frequencydef count_frequency_builtin(lst): return Counter(lst)large_list = [random.randint(0, 100) for _ in range(1000)]print(timeit.timeit(lambda: count_frequency_custom(large_list), number=100))# 0.005160166998393834print(timeit.timeit(lambda: count_frequency_builtin(large_list), number=100))# 0.002444291952997446
上面的程序比较了两种统计列表中元素频率的方法。可以看到,利用collections模块中内置的Counter函数比自己编写的for循环更快、更简洁、更好。
缓存是一种常用的技术,用于避免重复计算并加快程序的运行速度。
幸运的是,在大多数情况下,我们不需要自己编写缓存处理代码,因为Python为此提供了一个开箱即用的装饰器来实现这个目的——@functools.cache。
例如,下面的代码将执行两个生成斐波那契数的函数,一个有缓存装饰器,而另一个没有:
import timeitimport functoolsdef fibonacci(n): if n in (0, 1): return n return fibonacci(n - 1) + fibonacci(n - 2)@functools.cachedef fibonacci_cached(n): if n in (0, 1): return n return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)# 测试每个函数的执行时间print(timeit.timeit(lambda: fibonacci(30), number=1))# 0.09499712497927248print(timeit.timeit(lambda: fibonacci_cached(30), number=1))# 6.458023563027382e-06
结果证明了@functools.cache装饰器是如何使我们的代码变得更快的。
基本的fibonacci函数效率较低,因为在计算fibonacci(30)结果的过程中,它会多次重新计算相同的斐波那契数。
而使用缓存的版本要快得多,因为它缓存了之前的计算结果。因此,它只计算每个斐波那契数一次,并且对于相同的参数再次调用时会从缓存中获取结果。
仅仅添加一个内置的装饰器就可以带来如此大的改进,这就是Pythonic的意义所在。
本文链接:http://www.28at.com/showinfo-26-68332-0.html掌握这九个技巧,让Python代码快如闪电
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
下一篇: 自动化测试的十大误区,你知道哪个?