Python作为一种动态类型的解释型语言,与C等静态类型的编译型语言相比,执行速度可能会较慢。然而,通过特定的技术和策略,我们可以显著提升Python代码的性能。
本文将探讨如何优化Python代码,使其运行得更快、更高效。我们将使用Python的timeit
模块来精确测量代码的执行时间。
注意:默认情况下,timeit
模块会将代码执行100万次,以确保测量结果的准确性和稳定性。
def print_hi(name):
print(f'Hi, {name}')
if __name__ == '__main__':
# 执行print_hi('leapcell')方法
t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("leapcell")')
t.timeit()
time
模块中的time.perf_counter()
提供了高精度的计时器,适合测量短时间间隔。例如:
import time
# 记录程序开始时间
start_time = time.perf_counter()
# 你的代码逻辑
# ...
# 记录程序结束时间
end_time = time.perf_counter()
# 计算程序执行时间
run_time = end_time - start_time
print(f"程序执行时间: {run_time} 秒")
I/O密集型操作(Input/Output Intensive Operation)指的是执行时间大部分花费在等待输入输出操作完成的程序或任务。I/O操作包括从磁盘读取数据、向磁盘写入数据、网络通信等。这些操作通常涉及硬件设备,因此其执行速度受硬件性能和I/O带宽的限制。
它们的特征如下:
例如,使用I/O密集型操作print
,并执行100万次:
import time
import timeit
def print_hi(name):
print(f'Hi, {name}')
return
if __name__ == '__main__':
start_time = time.perf_counter()
# 执行print_hi('leapcell')方法
t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("leapcell")')
t.timeit()
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"程序执行时间: {run_time} 秒")
执行结果为3秒。
另一方面,如果不使用I/O操作,即不调用print()
,而是调用一个空的print_hi('xxxx')
方法,程序会显著加快:
def print_hi(name):
# print(f'Hi, {name}')
return
在代码中需要I/O操作时,例如文件读写,可以使用以下方法提高效率:
asyncio
等异步编程模型,使程序在等待I/O操作完成时可以继续执行其他任务,提高CPU利用率。自Python 2.7版本以来,列表、字典和集合的生成器得到了改进,使得数据结构的构建过程更加简洁高效。
def fun1():
list=[]
for i in range(100):
list.append(i)
if __name__ == '__main__':
start_time = time.perf_counter()
t = timeit.Timer(setup='from __main__ import fun1', stmt='fun1()')
t.timeit()
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"程序执行时间: {run_time} 秒")
# 输出结果: 程序执行时间: 3.363 秒
注意:为了方便起见,以下内容省略了主函数main
的代码部分。
def fun1():
list=[ i for i in range(100)]
# 程序执行时间: 2.094 秒
从上述推导式程序可以看出,这种方法不仅更简洁易懂,而且速度更快。因此,这种方法已成为生成列表和循环时的首选方法。
join()
join()
是Python的字符串方法,用于将序列中的元素连接(或合并)为字符串,通常使用特定的分隔符。其优点通常包括:
join()
是字符串连接中的高效方法,尤其适用于大量字符串。通常比使用+
运算符或%
格式化更快。在连接大量字符串时,join()
方法比逐个连接节省内存。join()
使代码更简洁,避免了重复的字符串连接操作。例如:
def fun1():
obj=['hello','this','is','leapcell','!']
s=""
for i in obj:
s+=i
# 程序执行时间: 0.35186 秒
使用join()
连接字符串时:
def fun1():
obj=['hello','this','is','leapcell','!']
"".join(obj)
# 程序执行时间: 0.1822 秒
使用join()
后,函数的执行时间从0.35秒缩短到0.18秒。
map()
替换循环在大多数场景中,传统的for
循环可以用更高效的map()
函数替换。map()
是Python内置的高阶函数,可以将指定的函数应用于列表、元组或字符串等可迭代数据结构。使用map()
的主要优点是提供了更简洁、高效的数据处理方式,避免了显式的循环代码。
def fun1():
arr=["hello", "this", "is", "leapcell", "!"]
new = []
for i in arr:
new.append(i)
# 程序执行时间: 0.3067 秒
map()
函数实现相同功能def fun2(x):
return x
def fun1():
arr=["hello", "this", "is", "leapcell", "!"]
map(fun2,arr)
# 程序执行时间: 0.1875 秒
通过比较可以看出,使用map()
可以节省近一半的时间,执行效率显著提高。
选择合适的数据结构对于提高Python代码的执行效率至关重要。不同的数据结构针对特定操作进行了优化。合理的选择可以加快数据的查找、添加和删除速度,提升程序的整体操作效率。
例如,在判断容器中的元素时,字典的查找效率高于列表,但这适用于大量数据的情况。对于少量数据,情况则相反。
def fun1():
arr=["hello", "this", "is", "leapcell", "!"]
'hello' in arr
'my' in arr
# 程序执行时间: 0.1127 秒
def fun1():
arr={"hello", "this", "is", "leapcell", "!"}
'hello' in arr
'my' in arr
# 程序执行时间: 0.1702 秒
使用numpy
生成100个随机整数:
import numpy as np
def fun1():
nums = {i for i in np.random.randint(100, size=100)}
1 in nums
# 程序执行时间: 14.28 秒
def fun1():
nums = {i for i in np.random.randint(100, size=100)}
1 in nums
# 程序执行时间: 13.53 秒
可以看出,对于少量数据,list
的执行效率高于dict
,但对于大量数据,dict
的效率高于list
。
如果存在频繁的添加和删除操作,且添加和删除的元素数量较多,list
的效率并不高。此时,可以考虑使用collections.deque
。collections.deque
是双端队列,具有栈和队列的特性,可以在两端进行插入和删除操作,时间复杂度为O(1)。
collections.deque
的使用方法from collections import deque
def fun1():
arr=deque()# 创建一个空的deque
for i in range(1000000):
arr.append(i)
# 程序执行时间: 0.0558 秒
def fun1():
arr=[]
for i in range(1000000):
arr.append(i)
# 程序执行时间: 0.06077 秒
list
的查找操作也非常耗时。如果需要频繁查找list
中的特定元素,或需要按顺序访问这些元素,可以使用bisect
来维护list
对象的顺序,并在其中进行二分查找,从而提高查找效率。
在Python编程中,优化函数调用次数对提高代码效率至关重要。过多的函数调用不仅会增加开销,还可能导致额外的内存消耗,从而降低程序的执行速度。为了提高性能,应尽量减少不必要的函数调用,尝试将多个操作合并为一个。这样可以减少执行时间和资源消耗。这种优化策略有助于编写更高效、更快速的代码。
import
Python的import
语句相对较快,但每次import
都涉及模块查找、模块代码执行(如果尚未执行)以及将模块对象放入当前命名空间的操作。这些操作需要一定的时间和内存。不必要的模块导入会增加这些开销。
import math
size=10000
def fun1():
for i in range(size):
for j in range(size):
z = math.sqrt(i) + math.sqrt(j)
# 程序执行时间: 15.6336 秒
许多程序员最初会用Python编写一些简单的脚本。编写脚本时,通常会像上面的代码一样直接写成全局变量。然而,由于全局变量和局部变量的实现方式不同,在全局作用域中定义的代码比在函数中定义的代码执行速度要慢得多。将脚本语句放入函数中,通常可以实现15% - 30%的速度提升。
import math
def fun1():
size = 10000
for i in range(size):
for j in range(size):
z = math.sqrt(i) + math.sqrt(j)
# 程序执行时间: 14.9319 秒
import math # 不推荐
def fun2(size: int):
result = []
for i in range(size):
result.append(math.sqrt(i))
return result
def fun1():
size = 10000
for _ in range(size):
result = fun2(size)
# 程序执行时间: 10.1597 秒
每次使用.
(属性访问运算符)时,都会触发__getattribute__()
或__getattr__()
等特定方法。这些方法执行字典操作,因此会带来额外的时间开销。通过使用from import
语句,可以消除属性访问。
from math import sqrt # 推荐:仅导入所需模块
def fun2(size: int):
result = []
for i in range(size):
result.append(sqrt(i))
return result
def fun1():
size = 10000
for _ in range(size):
result = fun2(size)
# 程序执行时间: 8.9682 秒
for
循环中的计算import math
def fun1():
size = 10000
sqrt = math.sqrt
for x in range(size):
for y in range(size):
z = sqrt(x) + sqrt(y)
# 程序执行时间: 14.2634 秒
在上述代码中,sqrt(x)
位于内部for
循环中,每次循环都会重新计算,增加了不必要的时间开销。
import math
def fun1():
size = 10000
sqrt = math.sqrt
for x in range(size):
sqrt_x=sqrt(x)
for y in range(size):
z = sqrt_x + sqrt(y)
# 程序执行时间: 8.4077 秒
通过将sqrt(x)
移出内部循环,执行时间从14.26秒减少到8.40秒。
通过以上优化策略,我们可以显著提升Python代码的执行效率。