写在前面
在编程语言的世界中,Python 作为一种应用最为广泛的语言之一,以其简洁易读的特点而闻名。由于其易读的语法、面向对象的特性、社区支持和丰富的库资源,Python 在开发者中变得越来越受欢迎。它可以应用于数据分析、人工智能、Web 开发、游戏开发等多个领域。
然而,与任何其他编程语言一样,Python 的优点也伴随着一些问题。其中之一就是性能优化。由于 Python 是一种解释型语言,人们对其速度和性能存在一些担忧。这就给了代码优化的用武之地。
什么是代码优化?
Python 是一种解释型语言,这意味着它不能像 C 语言或 C++ 等编译型语言那样运行得很快。然而,你可以利用某些技术和策略来优化你的 Python 代码,提高其性能。
代码优化可以使你的代码运行更快、使用更少的资源和更顺畅的执行,从而提高其性能和效率。
本文描述了让你的 Python 代码运行更快、更智能、更高效的艺术与方法。你将学习各种技术、策略和最佳实践,帮助开发者充分发挥 Python 的潜力,同时克服其中的一些性能限制。
我们将使用 Python 的 timeit 模块来对比传统 Python 代码和优化后的 Python 代码的执行时间。请注意,timeit 模块默认运行函数一百万次。
言归正传,让我们直接进入优化技术和策略。
一、使用推导式和生成器
在 Python 2.7 和 3.0 版本中,Python引入了推导式(comprehensions)等功能,包括列表推导式(List comprehension)、字典推导式(Dictionary comprehension)和紧密相关的集合推导式(Set comprehension)。这些功能让我们可以更简洁、更高效的生成列表、字典和集合。
>>> def do_1():
... list_object = []
... for i in range(100):
... list_object.append(i)
导入 Python 的内置 timeit 模块,看一下这个函数运行多长时间:
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do_1', stmt='do_1()')
>>> print(t.timeit())
9.681053700041957
上面的输出显示,这个函数运行大约需要9.68秒。
现在,使用推导式生成这个列表,看看需要多长时间:
>>> def do():
... [i for i in range(100)]
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
7.292758799972944
从上面的代码可以看出,这个函数的运行时间约为 7.29 秒,而之前的函数(不使用推导式)需要 9.68 秒,两者相差 2.39 秒。
除了推导式更加简洁易读外,它还更快。这使得它成为生成列表和循环的首选方法。
二、避免字符串拼接
许多开发者喜欢使用+=
运算符对字符串进行拼接。然而,在循环中这样做可能会很慢,因为在 python 中字符串是不可变
作为替代,我们可以使用 str.join() 方法进行高效的字符串连接。下面我们就两种方法的执行时间进行对比。
使用 += 运算符进行字符串连接并查看其执行时间:
>>> def do():
... obj = ["hello", "my", "name", "is", "Delight", "!"]
... s = ""
... for elem in obj:
... s += elem
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
0.9870554000372067
该函数使用 += 运算符实现字符串连接,大约需要 0.98 秒的时间来完成。
下面改用 join() 进行有效的字符串连接:
>>> def do():
... s = ["hello", "my", "name", "is", "Delight", "!"]
... "".join(s)
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
0.38973289995919913
使用 join() 将函数的执行时间从 0.98 秒减少到 0.38 秒,这使它成为更快和更可取的字符串连接方法。
三、循环
在 Python 中,for 循环是一种控制结构,允许您迭代一个序列中的项目,例如列表中的元素、字符串中的字符或其他可迭代对象中的元素。for 循环针对序列中的每个项目重复执行一段代码块,从第一个项目迭代到最后一个项目。
然而,在大多数情况下,for 循环可以被更高效的函数 map() 替代。map() 函数是一种内置的高阶函数,允许您将给定的函数应用于可迭代对象(如列表、元组或字符串)中的每个项目,并生成一个包含将该函数应用于每个项目的结果的新可迭代对象。使用 map() 的主要优势是它提供了一种简洁高效的方式来转换数据,而无需显式循环。
比较 for 循环和 map() 的函数的执行时间:
def do():
obj = ["hello", "my", "name", "is", "Delight", "!"]
new = []
for i in obj:
new.append(i.upper())
获取该函数的执行时间:
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
1.042804399970919
下面,使用内置的 map() 函数实现相同的功能,并获取执行时间:
>>> def square(x):
... return x.upper()
>>> def do():
... obj = ["hello", "my", "name", "is", "Delight", "!"]
... map(square, obj)
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
0.37273399997502565
在上面的代码中,使用 map() 而不是 for 循环使函数的运行速度提高了约 3 倍。
Python 的 map() 内置函数使我们的代码更快,主要是因为它们是用 C 语言编写和编译的。
四、选择正确的数据结构
选择正确的数据结构对于提高 Python 代码的速度和效率具有重要影响。不同的数据结构针对特定类型的操作进行了优化,选择适当的数据结构可以实现更快的查找、插入、删除和整体性能改进。
例如,使用集合进行成员测试比使用列表要快得多:
>>> def do():
... fruits_list = ['apple', 'banana', 'orange', 'grape', 'pear']
... 'banana' in fruits_list
... 'kiwi' in fruits_list
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
0.48580530006438494
>>> def do():
... fruits_list = {'apple', 'banana', 'orange', 'grape', 'pear'}
... 'banana' in fruits_set
... 'kiwi' in fruits_set
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import do', stmt='do()')
>>> t.timeit()
0.34570479998365045
五、减少使用全局变量
全局变量让你能够轻松的在程序中共享数据。然而,我们谨慎使用使用,只在必要时使用。访问全局变量比访问局部变量更慢,要始终尽量减少使用全局变量的情况,特别是在循环内部。
六、向量化
Python 中的向量化是指对整个数组或数据序列应用操作的实践,而不是使用显式循环迭代单个元素。它利用诸如 NumPy 之类的专门库,在数组上高效执行逐元素操作,利用硬件级别的优化,减少对显式循环结构的需求。
如果进行数值计算,请考虑使用像 NumPy 这样的库,它提供了经过优化的数组操作,比在标准 Python 列表中逐元素操作要快得多。
向量化是科学计算和数值分析的基本概念,在使 Python 成为强大的数据分析、机器学习和其他涉及大型数据集的计算任务的语言中起着关键作用。
七、避免不必要的函数调用
避免在 Python 中进行不必要的函数调用,对于提高代码的效率和性能非常重要。函数调用可能会引入开销,消耗内存,并降低程序的执行速度。尽量合并不必要的调用操作。
八、避免不必要的导入语句
import statements can be executed just about anywhere. It’s often useful to place them inside functions to restrict their visibility and/or reduce initial startup time. — Python.org
在 Python 中避免不必要的导入语句对于保持干净、高效和可读的代码至关重要。不必要的导入有时可能导致模块之间的循环依赖。这可能会在运行时引发问题,并使重构代码变得更加困难。
尽管现代文本编辑器现在可以很容易地帮助您找到程序中未使用的代码,但添加未使用的导入可能会使这些编辑器混淆,增加误判的风险。
九、编译您的代码
如果性能是你的程序执行的关键因素,请考虑使用像 Cython 这样的工具将您的代码编译为 C 或 C++。这可以显著提高程序的性能。
通过使用 Cython 等工具,您可以充分利用 Python 的易用性和 C 或 C++ 等编译语言的高性能特性。
十、分析你的代码
The first step to speeding up your program is learning where the bottlenecks lie. — Python.org
在 Python 中进行性能分析就是是测量和分析代码的执行时间和资源使用情况,以查找代码中的瓶颈和优化机会。
Python 提供了各种内置的性能分析工具,如 profile 和 timeit(我们已经使用过)。
以下是使用 profile 模块对一个名为 do() 的函数进行性能分析的示例:
import profile
profile.run('do()')
十一、及时了解Python的更新发布
较新的 Python 版本通常包括性能增强、错误修复、安全更新等。
Python 不断发展,新版本通常包含性能改进。保持更新的解释器可以帮助您从这些改进中受益。
然而,重要的是在保持最新状态和考虑升级对现有项目的影响之间取得平衡。如果您有一个庞大的代码库或依赖于尚不与最新版本兼容的第三方库,可能需要谨慎升级。
写在最后
在本文中,我们介绍了各种技术、策略和最佳实践,使开发人员能在克服 Python 某些性能限制的同时,充分发挥其潜力。
需要注意的是,优化不仅仅是从代码的执行时间中减少毫秒或微秒,而是编写可读性强、可扩展性好和可维护性高的代码。
原文始发于微信公众号(harvey的网络日志):Python优化指南:让你代码5X倍速
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/188212.html