Python代码优化技巧和窍门
- 1-分析你的代码
- 1.1. 使用timeit模块1.2. 使用高级的cProfile分析1.2.1. 关于 cProfile 的结果说明?
私信小编01即可获取Python学习资料
- 2-使用生成器和键进行排序
- 3-优化你的循环语句
- 3.1. 在Python中优化for循环
- 4-利用哈希
- 5-避免使用全局变量
- 6-使用外部的包或者库
- 7-使用内置的运算符
- 8-限制循环中的方法调用
- 9-字符串优化
- 10-if语句进行优化
- 11-使用装饰器进行一些缓存操作
- 12-将“ while 1”用于无限循环。
Python是一种功能强大的编程语言。 我们可以做很多事情来使我们的代码更轻,更快。 它不仅仅是使用多进程等功能,而且还可以轻松实现。 下面,我们列出了一些最佳的Python代码优化技巧和窍门。
1-分析你的代码
如果您不了解你的代码性能瓶颈所在,那么在进一步优化代码之前,这会显得你很幼稚。因此,首先,使用以下两种方法中的任何一种来分析您的代码
1.1. 使用timeit模块
下面是使用Python的模块进行分析的传统方式。它记录了一段代码执行所需的时间。及测量过程消耗的时间(以毫秒为单位)
import timeit
subStrings = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
def simpleString(subStrings):
finalString = ''
for part in subStrings:
finalString += part
return finalString
def formatString(subStrings):
finalString = "%s%s%s%s%s%s%s" % (subStrings[0], subStrings[1],
subStrings[2], subStrings[3],
subStrings[4], subStrings[5],
subStrings[6])
return finalString
def joinString(subStrings):
return ''.join(subStrings)
print('joinString() Time : ' + str(
timeit.timeit('joinString(subStrings)', setup='from __main__ import joinString, subStrings')))
print('formatString() Time : ' + str(
timeit.timeit('formatString(subStrings)', setup='from __main__ import formatString, subStrings')))
print('simpleString() Time : ' + str(
timeit.timeit('simpleString(subStrings)', setup='from __main__ import simpleString, subStrings')))
结果如下:
joinString() Time : 0.223614629
formatString() Time : 0.49615162100000004
simpleString() Time : 0.47305408300000007
上面的示例说明了join方法比其他方法效率更高。
1.2. 使用高级的cProfile分析
从Python 2.5开始,cProfile已成为Python软件包的一部分。 它带来了一套不错的分析功能。 您可以通过多种方式将其与代码绑定。 就像将一个函数包装在其run方法中以衡量性能。 或者,借助Python的“ -m”选项将cProfile作为参数激活,同时以命令行方式运行整个脚本。
import cProfile
def add():
"""
也可使用双引号,当然引号里面也可以是直接的表达式 例如 '10 + 10'这样的用法
"""
return 10 + 10
cProfile.run('add()')
结果如下:
3 function calls in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
当然也可以在terminal 里面使用命令:python -m cProfile -s cumtime xxx.py
1.2.1. 关于 cProfile 的结果说明?
从输出中分析找到导致代码耗时罪魁祸首就显得尤为重要。因此只有知道cProfile报告的关键要素后才能做出判断;
1. ncalls:表示函数调用的次数;
2. tottime:表示指定函数的总的运行时间;
3. percall:(第一个percall)表示tottime除以ncalls;
4. cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
5 .percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
5. filename:lineno(function):每个函数调用的具体信息;
从分析报告中你可以找到具体的原因。当然首先;最重要的是tottime和cumtime。 ncalls有时也可能是有用的。对于其余项目,您需要自己练习分析;
2-使用生成器和键进行排序
生成器是内存优化的绝佳工具。 它会创建一个可以一次返回一个结果的(迭代器)的函数,而不是一次返回所有的结果。 一个很好的例子是创建大量数字并将它们相加。
同样,在对列表中的元素进行排序时,应尽可能使用键和默认的sort()方法,在下面的例子中,我们根据key参数选择部分的索引对列表进行排序。
import operator
test = [(11, 52, 83), (61, 20, 40), (93, 72, 51)]
print("Before sorting:", test)
test.sort(key=operator.itemgetter(0))
print("After sorting[1]: ", test)
test.sort(key=operator.itemgetter(1))
print("After sorting[2]: ", test)
test.sort(key=operator.itemgetter(2))
print("After sorting[3]: ", test)
结果如下:
Before sorting: [(11, 52, 83), (61, 20, 40), (93, 72, 51)]
After sorting[1]: [(11, 52, 83), (61, 20, 40), (93, 72, 51)]
After sorting[2]: [(61, 20, 40), (11, 52, 83), (93, 72, 51)]
After sorting[3]: [(61, 20, 40), (93, 72, 51), (11, 52, 83)]
3-优化你的循环语句
大多数编程语言都强调需要优化循环。在Python中,我们确实有一种方法可以使循环执行得更快。
虽然您可能喜欢使用循环,但是循环是有代价的。 Python引擎在解释for循环结构上花费了大量精力。因此,最好将它们替换为Python的内置函数(例如Map)
接下来,代码优化的级别还取决于您对Python内置功能的了解。在以下示例中,我们将尝试解释不同的方法以帮助优化循环。
3.1. 在Python中优化for循环
import timeit
import itertools
Zipcodes = ['121212','232323','434334']
newZipcodes = [' 131313 ',' 242424 ',' 212121 ',' 323232','342312 ',' 565656 ']
def updateZips(newZipcodes, Zipcodes):
"""
Example-1 :最原始的,利用for循环去除 newZipcodes 里面的空格
:param newZipcodes:
:param Zipcodes:
:return:
"""
for zipcode in newZipcodes:
Zipcodes.append(zipcode.strip())
def updateZipsWithMap(newZipcodes, Zipcodes):
"""
Example-2 :现在,看看如何使用map对象将以上内容转换为一行。在查看具体收益有多大
:param newZipcodes:
:param Zipcodes:
:return:
"""
Zipcodes += map(str.strip, newZipcodes)
def updateZipsWithListCom(newZipcodes, Zipcodes):
"""
Example-3 :利用列表推导式
:param newZipcodes:
:param Zipcodes:
:return:
"""
Zipcodes += [iter.strip() for iter in newZipcodes]
def updateZipsWithGenExp(newZipcodes, Zipcodes):
"""
Example-3 :最后,最快的方法是将for循环转换为生成器表达式
:param newZipcodes:
:param Zipcodes:
:return:
"""
return itertools.chain(Zipcodes, (iter.strip() for iter in newZipcodes))
print('updateZips() Time : ' + str(timeit.timeit('updateZips(newZipcodes, Zipcodes)', setup='from __main__ import updateZips, newZipcodes, Zipcodes')))
Zipcodes = ['121212','232323','434334']
print('updateZipsWithMap() Time : ' + str(timeit.timeit('updateZipsWithMap(newZipcodes, Zipcodes)', setup='from __main__ import updateZipsWithMap, newZipcodes, Zipcodes')))
Zipcodes = ['121212','232323','434334']
print('updateZipsWithListCom() Time : ' + str(timeit.timeit('updateZipsWithListCom(newZipcodes, Zipcodes)', setup='from __main__ import updateZipsWithListCom, newZipcodes, Zipcodes')))
Zipcodes = ['121212','232323','434334']
print('updateZipsWithGenExp() Time : ' + str(timeit.timeit('updateZipsWithGenExp(newZipcodes, Zipcodes)', setup='from __main__ import updateZipsWithGenExp, newZipcodes, Zipcodes')))
updateZips() Time : 1.043096744
updateZipsWithMap() Time : 0.7813633710000001
updateZipsWithListCom() Time : 0.9924229019999999
updateZipsWithGenExp() Time : 0.5045337760000002
如上所述,在上述用例中(通常),使用生成器表达式是优化for循环的最快方法。我们汇总了四个示例的代码,以便您还可以看到每种方法所获得的性能提升.
4-利用哈希
Python使用哈希表来管理集合。每当我们将元素添加到集合中时,Python解释器都会使用目标元素的哈希值确定其在分配给该集合的内存中的位置。
由于Python自动调整哈希表的大小,因此无论集合的大小如何,速度都可以恒定(O(1))这就是使设置操作执行得更快的原因。
在Python中,集合操作包括并集,交集和差集。因此,您可以尝试在适合它们的代码中使用它们。这些通常比遍历列表更快。具体用法百度
Syntax Operation Description
------ --------- -----------
set(l1)|set(l2) Union Set with all l1 and l2 items.
set(l1)&set(l2) Intersection Set with commmon l1 and l2 items.
set(l1)-set(l2) Difference Set with l1 items not in l2.
5-避免使用全局变量
不仅限于Python,几乎所有语言都不赞成过度或无节制地使用全局变量。 其背后的原因是它们可能具有导致代码一些非显而易见的副作用。 而且,Python在访问外部变量方面确实很慢。
使用很少的全局变量是一种有效的设计模式,因为它可以帮助您跟踪范围和不必要的内存使用情况。而且,Python检索局部变量要比全局变量更快。
6-使用外部的包或者库
一些python库具有与原始库相同的功能。比如用“ C”编写的代码执行速度更快。例如,尝试使用cPickle而不是使用pickle。也可以尝试使用cpython。
您也可以考虑使用PyPy软件包。它包括一个JIT(即时)编译器,使Python代码运行得非常快。您甚至可以对其进行调整以提供额外的处理能力。
7-使用内置的运算符
Python是一种解释性语言,基于高级抽象。 因此,您应尽可能使用内置功能。 由于内置程序是预先编译的,并且速度很快,因此可以提高您的代码效率。 而包括解释步骤在内的冗长迭代变得非常缓慢。
同样,多使用map等内置功能,这些功能可以显着提高速度。
8-限制循环中的方法调用
当在循环中执行操作时,应该缓存方法调用,而不是在对象上调用它。否则,方法查找会显得很昂贵。
如下示例:
>>> for it in xrange(10000):
>>> myLib.findMe(it)
>>> findMe = myLib.findMe
>>> for it in xrange(10000):
>>> findMe(it)
9-字符串优化
字符串拼接速度很慢,永远不要在循环内进行。相反,请使用Python的join方法。或者,使用格式设置功能来形成统一的字符串
随着Python中的RegEx,虽然它们的运行速度很快。但是,在某些情况下,像isalpha()/isdigit()/ startswith()/ endswith()这样的基本字符串使用方法会更好
10-if语句进行优化
就像大多数编程语言都允许进行惰性假设评估一样,Python也是如此。这意味着,像“ AND”条件,如果其中任何一个条件为假,则不会对后续条件进行测试;
多使用if x:,而不是if x == True:来进行比较
if done is not None比使用 if done != None 更快
11-使用装饰器进行一些缓存操作
当我使用该算法找到第36个斐波那契数,即fibonacci(36)时,计算过程花了12s,48315636 function calls.
import cProfile
import timeit
def fibonacci(n):
if n == 0: # There is no 0'th number
return 0
elif n == 1: # We define the first number as 1
return 1
return fibonacci(n - 1) + fibonacci(n-2)
#
# print('fibonacci() Time : ' + str(
# timeit.timeit('fibonacci(36)', setup='from __main__ import fibonacci, n')))
cProfile.run('fibonacci(36)')
48315636 function calls (4 primitive calls) in 12.584 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 12.584 12.584 <string>:1(<module>)
48315633/1 12.584 0.000 12.584 12.584 vv.py:4(fibonacci)
1 0.000 0.000 12.584 12.584 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
但是,当从标准库引入缓存时,情况会发生变化。只需要几行代码
import cProfile
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)
cProfile.run('fibonacci(100)')
104 function calls (4 primitive calls) in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
101/1 0.000 0.000 0.000 0.000 vv.py:6(fibonacci)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
计算第100个数的时间0s,104 function calls
12-将“ while 1”用于无限循环。
如果您正在侦听套接字,则可能需要使用无限循环。实现此目的的正常方法是在True时使用。这是可行的,但是通过使用while 1可以更快地达到相同的效果,因为它是一个数值比较,仅适用Python2。
因为,在Python 2.x中,True它不是关键字,而只是在类型中定义为1 的内置全局常量bool。因此,解释器仍然必须加载True的内容。换句话说,True是可重新分配的:
Python 2.7 (r27:82508, Jul 3 2010, 21:12:11)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 4
>>> True
4
在Python 3.x中,
Python 3.1.2 (r312:79147, Jul 19 2010, 21:03:37)
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> True = 4
File "<stdin>", line 1
SyntaxError: assignment to keyword