“Python太慢了。”
这种观点在关于编程语言的讨论中经常出现,常常掩盖了Python的诸多优点。
事实上,如果你能以Pythonic的方式编写代码,Python其实是快速的。
关键在于细节。经验丰富的Python开发者掌握着一系列微妙而强大的技巧,以显著提升他们代码的性能。
这些技巧乍一看可能看起来微不足道,但它们可以带来效率上的实质性改进。让我们深入探讨这9种方法,转变你编写和优化Python代码的方式。
1、更快的字符串连接:技巧地选择“join()”或“+” 如果有大量字符串等待处理,字符串连接将成为你Python程序的瓶颈。
Python中有两种字符串连接方式:
-
使用函数将字符串列表组合成一个
join()
-
使用
+
或+=
符号将每个单独的字符串加成一个
那么哪种方式更快呢?
说起来容易,让我们定义3个不同的函数来连接相同的字符串:
pythonCopy codemylist = ["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"
根据你的第一印象,你认为哪个函数最快,哪个最慢?
真实结果可能会让你感到惊讶:
pythonCopy codeimport timeit`` ``print(timeit.timeit(concat_plus, number=10000))``# 0.002738415962085128``print(timeit.timeit(concat_join, number=10000))``# 0.0008482920238748193``print(timeit.timeit(concat_directly, number=10000))``# 0.00021425005979835987
如上所示,对于连接字符串列表,join()
方法比在循环中一个接一个添加字符串的方式更快。
原因很简单。一方面,字符串在Python中是不可变数据,每次操作都会创建一个新字符串并复制旧字符串,这在计算上是昂贵的。另一方面,join()
方法针对连接字符串序列进行了特别优化。它预先计算结果字符串的大小,然后一次性构建它。因此,它避免了在循环中使用+=
操作的开销,所以更快。
然而,在我们的测试中最快的函数是直接连接字符串字面量。它的高速度归因于:
-
Python解释器可以在编译时优化字符串字面量的连接,将它们转换成单个字符串字面量。没有循环迭代或函数调用涉及,使其成为非常高效的操作。
-
由于所有字符串在编译时都是已知的,Python可以非常快速地执行此操作,比循环中的运行时连接甚至优化的
join()
方法都要快。
总之,如果你需要连接字符串列表,选择join()
而不是+=
。如果你想直接连接字符串,只需使用+
来完成。
2、更快的列表创建:使用“[]”而不是“list()”
创建列表并不是什么大问题。两种常见的方式是:
-
使用
list()
函数 -
直接使用
[]
让我们使用一个简单的代码片段来测试它们的性能:
pythonCopy codeimport timeit`` ``print(timeit.timeit('[]', number=10 ** 7))``# 0.1368238340364769``print(timeit.timeit(list, number=10 ** 7))``# 0.2958830420393497
如结果所示,执行list()
函数比直接使用[]
慢。
这是因为[]
是一个字面量语法,而list()
是一个构造器调用。毫无疑问,调用函数需要额外的时间。
同样的逻辑,当创建字典时,我们也应该优先使用{}
而不是dict()
。
3、更快的成员检测:使用集合而set不是列表list
成员检测操作的性能在很大程度上取决于底层数据结构:
pythonCopy codeimport timeit`` ``large_dataset = range(100000)``search_element = 2077`` ``large_list = list(large_dataset)``large_set = set(large_dataset)`` `` ``def list_membership_test():` `return search_element in large_list`` `` ``def set_membership_test():` `return search_element in large_set`` `` ``print(timeit.timeit(list_membership_test, number=1000))``# 0.01112208398990333``print(timeit.timeit(set_membership_test, number=1000))``# 3.27499583363533e-05
如上述代码所示,集合中的成员测试比列表中快得多。
为什么会这样呢?
在Python列表中,成员检测(element in list
)是通过迭代每个元素直到找到所需元素或到达列表末尾来完成的。因此,这种操作的时间复杂度为O(n)。
Python中的集合是作为哈希表实现的。检查成员时(element in set
),Python使用哈希机制,其时间复杂度平均为O(1)。
这里的重点是,在编写程序时要仔细考虑底层数据结构。利用正确的数据结构可以显著加快我们代码的速度。
4、更快的数据生成:使用推导式而不是For循环
Python中有四种类型的推导式:列表(list)、字典(dictionary)、集合(set)和生成器(generator)。它们不仅提供了一种更简洁的语法来创建相关数据结构,而且比使用循环的性能更好。因为它们在Python的C实现中进行了优化。
pythonCopy codeimport timeit`` ``def generate_squares_for_loop():` `squares = []` `for i in range(1000):` `squares.append(i * i)` `return squares`` ``def generate_squares_comprehension():` `return [i * i for i in range(1000)]`` ``print(timeit.timeit(generate_squares_for_loop, number=10000))``# 0.2797503340989351``print(timeit.timeit(generate_squares_comprehension, number=10000))``# 0.2364629579242319
上述代码是列表推导式和for循环之间的一个简单速度比较。结果显示,列表推导式更快。
5、更快的循环:优先考虑局部变量
在Python中,访问局部变量比访问全局变量或对象的属性更快。
这是一个证明这一点的例子:
pythonCopy codeimport timeit`` ``class Example:` `def __init__(self):` `self.value = 0`` ``obj = Example()`` ``def test_dot_notation():` `for _ in range(1000):` `obj.value += 1`` ``def test_local_variable():` `value = obj.value` `for _ in range(1000):` `value += 1` `obj.value = value`` ``print(timeit.timeit(test_dot_notation, number=1000))``# 0.036605041939765215``print(timeit.timeit(test_local_variable, number=1000))``# 0.024470250005833805
这就是Python的工作方式。直观地说,当一个函数被编译时,它内部的局部变量是已知的,但其他外部变量需要时间来检索。
这是一个小问题,但当处理大量数据时,我们可以利用它来优化我们的代码。
6、更快的执行:优先选择内置模块和库
当工程师说起Python时,默认指的是CPython。因为CPython是Python语言最常用的实现方式。
考虑到大多数内置模块和库都是用C语言编写的,C语言是一种更快更低级的语言,我们应该利用内置的工具库,避免重复造轮子。
pythonCopy codeimport timeit``import random``from collections import Counter`` ``def count_frequency_custom(lst):` `frequency = {}` `for item in lst:` `if item in frequency:` `frequency[item] += 1` `else:` `frequency[item] = 1` `return frequency`` ``def 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.005160166998393834``print(timeit.timeit(lambda: count_frequency_builtin(large_list), number=100))``# 0.002444291952997446
上面的程序比较了两种计算列表中元素频率的方法。我们可以看到,利用collections
模块中的Counter
内置函数更快、更整洁、更好,而不是自己编写for循环。
7、更快的函数调用:利用缓存装饰器进行简易的记忆化
缓存是一种常用的技术,用于避免重复计算并加速程序。
幸运的是,大多数情况下我们不需要编写自己的缓存处理代码,因为Python提供了一个开箱即用的装饰器来实现这一目的——@functools.cache
。
例如,下面的代码将执行两个斐波那契数生成函数,一个使用了缓存装饰器,另一个没有:
pythonCopy codeimport timeit``import functools`` ``def fibonacci(n):` `if n in (0, 1):` `return n` `return fibonacci(n - 1) + fibonacci(n - 2)`` ``@functools.cache``def 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.09499712497927248``print(timeit.timeit(lambda: fibonacci_cached(30), number=1))``# 6.458023563027382e-06
结果证明了functools.cache
装饰器如何使我们的代码更快。
基本函数效率低下,因为在获取fibonacci(30)
的结果过程中,它多次重复计算相同的斐波那契数。
缓存版本显著更快,因为它缓存了之前计算的结果。因此,它只计算每个斐波那契数一次,随后带有相同参数的调用从缓存中检索。
仅仅添加一个内置装饰器就可以带来如此巨大的改进,这就是所谓的Pythonic。😎
8、更快的无限循环:优先使用“while 1”而不是“while True”
要制造一个无限的while循环,我们可以使用while True
或while 1
。
它们之间性能的差异通常可以忽略不计。但了解while 1
稍微更快总是有趣的。
这源于1
是字面量,而True
是一个需要在Python的全局作用域中查找的全局名称,因此需要一点点额外的开销。
让我们也在代码片段中检查这两种方式的真实比较:
pythonCopy codeimport timeit`` ``def loop_with_true():` `i = 0` `while True:` `if i >= 1000:` `break` `i += 1`` ``def loop_with_one():` `i = 0` `while 1:` `if i >= 1000:` `break` `i += 1`` ``print(timeit.timeit(loop_with_true, number=10000))``# 0.1733035419601947``print(timeit.timeit(loop_with_one, number=10000))``# 0.16412191605195403
正如我们所看到的,while 1
确实略微更快。
然而,现代Python解释器(如CPython)高度优化,这种差异通常是微不足道的。因此,我们不需要担心这种可以忽略的差异。更不用说while True
比while 1
更具可读性。
9、更快的启动:智能地导入Python模块
在Python脚本的顶部导入所有模块似乎是自然的。
实际上,我们不必那样做。
此外,如果一个模块太大,根据需要导入它是一个更好的主意。
pythonCopy codedef my_function():` `import heavy_module` `# 函数的其余部分
如上代码所示,heavy_module
是在一个函数内部导入的。这是“懒加载”的一个想法,其中导入被推迟到my_function
被调用时。
这种方法的好处是,如果在我们脚本的执行过程中从未调用my_function
,那么heavy_module
就永远不会被加载,从而节省资源并减少我们脚本的启动时间。
以上就是“9 个微妙的技巧,让你的 Python 代码更快”的全部内容,希望对你有所帮助。
关于Python技术储备
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
三、Python视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、Python练习题
检查学习结果。
六、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
最后祝大家天天进步!!
上面这份完整版的Python全套学习资料已经上传至CSDN官方,朋友如果需要可以直接微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】。