原文:Python Performance Tuning: 20 Simple Tips | Kevin Cunningham
1. 使用列表解析
当你在 Python 中工作时,循环是常见的。你以前可能遇到过列表推导式。它们是创建新列表的一种简洁而快速的方式。
例如,查找给定范围内所有奇数的立方体。使用 for
循环,如下所示:
cube_numbers = []
for n in range(0,1000000):
if n % 2 == 1:
cube_numbers.append(n**2)
# 运行 10 次时长:2.8s
相反,一行代码解析列表:
cube_numbers = [n**3 for n in range(1,1000000) if n%2 == 1]
# 运行 10 次时长:2.7s
解析列表更短,更简洁。运行明显更快。在小型代码库,这些方法没有差异。但你想节省时间时,会好很多。
# 使用 map + lambda:
cube_numbers = list(map(lambda x: x ** 2, range (1,100000)))
# 运行 10 次时长:0.43s
2. 记住内置函数
使用基础函数库。阅读内置函数列表。
3. 使用 xrange() 替换 range()
Python 3 已经默认实现。
4. 编写自己的生成器
前面的技巧是优化的一般模式。使用生成器,可以一次返回一个项目,而不是一次返回所有项目。
如果使用列表,请考虑编写自己的生成器,利用延迟加载和内存效率。当读取大量大文件时,生成器特别有用。
递归进行 Web 爬虫示例:
import requests
import re
def get_pages(link):
pages_to_visit = []
pages_to_visit.append(link)
pattern = re.compile('https?')
while pages_to_visit:
current_page = pages_to_visit.pop(0)
page = requests.get(current_page)
for url in re.findall('<a href="([^"]+)">', str(page.content)):
if url[0] == '/':
url = current_page + url[1:]
if pattern.match(url):
pages_to_visit.append(url)
yield current_page
webpage = get_pages('http://www.example.com')
for result in webpage:
print(result)
此示例一次仅返回一个页面并执行某种动作。如果没有生成器,则需要在开始处理之前同时获取和处理或收集所有链接。此代码更干净,更快,更易于测试。
5. 尽可能使用 "in"
要检查列表是否含有成员,通常使用 in
关键字会更快。
for name in member_list:
print('{} is a member'.format(name))
6. 别急于导入模块
开始学习 Python 时,您可能会得到建议,在代码开头时导入所有模块。
这种方法很方便。但缺点是在启动时加载所有模块。
可以在需要时加载模块:有助于更均匀地分配模块的加载时间,减少内存使用峰值。
7. 使用集合(sets)和联合(unions)
关于列表处理,我多次提到过循环。大多数专家都认为,过多的循环会给服务器带来不必要的压力。尽量少用。
获得两个列表中的交集。可以使用嵌套的 for
循环,如下所示:
a = [1,2,3,4,5]
b = [2,3,4,5,6]
overlaps = []
for x in a:
for y in b:
if x==y:
overlaps.append(x)
print(overlaps)
# 返回[2,3,4,5]
另一种方法是:
a = [1,2,3,4,5]
b = [2,3,4,5,6]
overlaps = set(a) & set(b)
print(overlaps)
# 返回{2,3,4,5}
使用内置函数,大大提高速度。
8. 记住要使用多个赋值
Python 有一种优雅的方式给多个变量赋值。
first_name, last_name, city = "Kevin", "Cunningham", "Brighton"
也可以交换变量的值。
x, y = y, x
比下面的方法更快,更干净:
temp = x
x = y
y = temp
9. 避免使用全局变量
减少跟踪范围和不必要的内存使用情况。而且检索局部变量要比全局更快。因此,避免使用全局关键字。
10. 使用 join() 连接字符串
Python 可以使用 +
连接字符串。但 Python 字符串不可变,+
需要在每个步骤后创建新字符串并复制旧内容。更有效的方法是使用数组修改单个字符,然后使用 join()
函数连接。
new = "This" + "is" + "going" + "to" + "require" + "a" + "new" + "string" + "for" + "every" + "word"
print(new)
代码将会打印以下结果
Thisisgoingtorequireanewstringforeveryword
另外,这段代码
new = " ".join(["This", "will", "only", "create", "one", "string", "and", "we", "can", "add", "spaces."])
print(new)
将会打印以下结果
This will only create one string and we can add spaces.
翻译过来就是:这样只会创建一个字符串,还可以添加空格。 更干净,更优雅,更快捷。
11. 保持最新的 Python 版本
Python 维护人员热衷于不断提高语言的速度和健壮性。总的来说,该语言的每个新发行版都提高了 Python 的性能和安全性。只需先确定要使用的库就可以与最新版本兼容。
12. “while 1” 用于无限循环
如果在套接字上监听,那么可能需要使用无限循环。一般用 While True
。但用 while 1
可以更快地达到相同的效果。这是一次单跳转操作,因为它是比较数值。
13. 尝试另一种方式
一旦在应用程序中使用了一种编码方法,就很容易反复依赖该方法。然而,实验可以让您看到哪种技术更好。这不仅会让你不断学习和思考你所写的代码,而且还能鼓励你更有创新精神。考虑如何创造性地应用新的编码技术以在应用程序中获得更快的结果。
14. 早点退出
某个功能无法完成更多有意义的工作时,请尝试离开该功能。这样做可以减少程序的缩进并使它更具可读性。它还允许您避免嵌套 if
语句。
if positive_case: # 如果情况A
if particular_example: # 如果情况B
do_something
else:
raise exception
您可以在执行操作之前以几种方式测试输入。另一种方法是尽早引发异常,并在循环的其他部分执行主要操作。
if not positive_case: # 不是情况A
raise exception
if not particular_example: # 不是情况A
raise exception
do_something
现在可以一眼看出代码要实现的目标。您不需要遵循条件语句中的逻辑链。另外,您可以清楚地看到此函数何时会引发异常。
15. 学习 itertools
它被称为 宝石。 您可以使用 itertools 中的函数来创建快速,内存高效且美观的代码。
深入研究文档,并寻找可以 充分利用该库的 教程。一个例子是排列功能。假设您想生成 [“ Alice”,“ Bob”,“ Carol”] 的所有排列。
import itertools
iter = itertools.permutations(["Alice", "Bob", "Carol"])
list(iter)
此函数将返回所有可能的排列:
[('Alice', 'Bob', 'Carol'),
('Alice', 'Carol', 'Bob'),
('Bob', 'Alice', 'Carol'),
('Bob', 'Carol', 'Alice'),
('Carol', 'Alice', 'Bob'),
('Carol', 'Bob', 'Alice')]
非常有用且速度非常快!
16. 尝试装饰器缓存
Memoization 是一种特定的缓存类型,可以优化软件的运行速度。基本上,缓存存储操作的结果供以后使用。结果可以呈现为网页或复杂计算的结果。
您可以自己计算第 100 个斐波纳契数来尝试。1、1、2、3、5......
一种计算这些算法的算法是:
def fibonacci(n):
if n == 0: # 斐波纳契数不带0
return 0
elif n == 1: # 起始数字为1
return 1
return fibonacci(n - 1) + fibonacci(n-2)
计算到第 36 个斐波那契数,即 fibonacci(36)
时,我的计算机要起飞了!花了 5 秒钟,(如果您好奇的话)答案为 14,930,352。
但引入标准库缓存时。只需要几行代码。
import functools
@functools.lru_cache(maxsize=128)
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n-2)
在 Python 中,装饰器函数采用另一个函数并扩展其功能。用 @
符号表示这些功能。
在上面的示例中,使用 functools 模块提供的装饰器 functools.lru_cache 函数。传递要作为参数同时存储在缓存中的最大项目数。装饰器缓存还有其他形式,包括编写自己的缓存,但这是快速且内置的。这一次计算时间仅为 0.007 秒。
17. 使用 key 进行排序
排序项目的最佳方法是尽可能使用 key
和默认的 sort()
方法。我已经提到过,内置函数通常更快。
import operatormy_list = [("Josh", "Grobin", "Singer"), ("Marco", "Polo", "General"), ("Ada", "Lovelace", "Scientist")] my_list.sort(key=operator.itemgetter(0)) my_list
这将按前几个 key
对列表进行排序:
[('Ada', 'Lovelace', 'Scientist'), ('Josh', 'Grobin', 'Singer'), ('Marco', 'Polo', 'General')] # A < J < M
您可以轻松地按第二个键进行排序,如下所示:
my_list.sort(key=operator.itemgetter(1))
my_list
这将返回下面的列表。您可以看到它是按第二个名字排序的。
[('Josh', 'Grobin', 'Singer'), ('Ada', 'Lovelace', 'Scientist'), ('Marco', 'Polo', 'General')] # G < L < P
列表根据 key
进行排序。这种方法适用于数字和字符串,并且可读性强且速度快。
18. 不要为条件语句构造集合
想要优化以下类似的代码:
if animal in set(animals):
想法似乎是有道理的。可能有很多 animal,对它们进行重复数据删除可能会感觉更快。
if animal in animals:
即使列表中可能有更多的动物要检查,但解释器的优化程度仍然很高,以至于 set
函数很可能会减慢速度。在不使用 set
函数的情况下,长列表的检查 in
几乎总是更快的。
19. 使用链表(linked lists)
Python 列表数据类型实现方式为数组。这意味着将元素添加到列表的开头很费劲,因为其他项目都必须向后移动。
链表可能派上用场。它与数组不同,每个项目都有一个指向列表中下一个项目的链接,因此得名!
数组需要用于预先分配的列表的内存。这种分配可能既昂贵又浪费,特别是如果您事先不知道数组的大小。
链表可让您在需要时分配内存。每个项目都可以存储在内存的不同部分中,并且链接将这些项目连接在一起。
问题在于查找时间较慢。如何取舍看你。
20. 使用基于云的 Python 性能工具
在本地工作时,可以使用 性能分析工具 ,使您可以洞察应用程序中的瓶颈。但是,如果将您的应用程序部署到Web,则情况会有所不同。 Stackify 允许您查看您的应用程序在生产负载下的性能如何。它还提供代码概要分析,错误跟踪和服务器指标。跳转至 Python部分, 以了解如何将其与您的应用程序配合使用。