学过太极拳的朋友应该听过一句话:要把拳练好,必把圈练小。一般而言,功夫越深的人外形动作越小,编写代码也是同样的道理。在满足功能要求的前提下应该追求代码的短小精悍,而这也是对Python内功的考验。虽然不至于“两句三年的,一吟双泪流”,但确实也是需要经过反复推敲的。有不少人编写代码之前没有经过系统地规划,也没有经过深思熟虑,完全是凭着感觉走,想到哪写到哪,写的时候还反复地修改前面的代码。对于一些小的程序这样做是可以的,但是对于大型软件开发,这种“不管黑猫白猫,抓到老鼠就是好猫”的态度是可怕的。编写代码时,不仅要考虑功能,还要考虑性能以及可维护性、可扩展性、可移植性等。
虽然现在的计算机配置越来越高,内存越来越大,计算速度越来越快,计算能力越来越强,各种云平台更是提供了惊人的存储空间和计算能力,但作为一种美德,还是要合理利用资源,能少用一点资源就省一点,能让代码运行更快一点就尽量优化一下,同时也能给代码进行适当“减肥”来增加可读性。在编写循环语句时,应尽量减少循环内部不必要或无关的计算,将与循环变量无关的代码尽可能提取到循环之外,这样可以提高代码的执行效率。对于使用多重循环嵌套的情况,应尽量较少内层循环中不必要的计算,尽可能的向外提。例如,下面的代码,第二段明显比第一段的运行效率高。
1 import time
2
3 digits = (1,2,3,4)
4 start = time.time()
5 for i in range(1000):
6 result = []
7 for i in digits:
8 for j in digits:
9 for k in digits:
10 result.append(i ** 100 + j * 10 + k)
11
12 print('# 第一段代码执行耗时:{}'.format(time.time() - start))
13
14 start2 = time.time()
15 for i in range(1000):
16 result = []
17 for i in digits:
18 i *= 100
19 for j in digits:
20 j *= 10
21 for k in digits:
22 result.append(i + j + k)
23
24 print('# 第二段代码执行耗时:{}'.format(time.time() - start2))
25 print(result)
26
27 # 第一段代码执行耗时:0.062400102615356445
28 # 第二段代码执行耗时:0.031200170516967773
29
30 #[111, 112, 113, 114, 121, 122, 123, 124, 131, 132, 133, 134, 141, 142, 143, 144, 211, 212, 213, 214, 221, 222, 223, 224, 231, 232, 233,
234, 241, 242, 243, 244, 311, 312, 313, 314, 321, 322, 323, 324, 331, 332, 333, 334, 341, 342, 343, 344, 411, 412, 413, 414, 421, 422,
423, 424, 431, 432, 433, 434, 441, 442, 443, 444]
另外,在循环中应尽量引用局部变量,局部变量的查询和访问速度比全局变量略快,在使用模块中的方法时,可以通过将其转换为局部变量来提高运行速度。例如下面的代码:
1 import time
2 import math
3
4 start = time.time()
5 for i in range(10000000):
6 math.sin(i)
7
8 print('# Time Used 1:',time.time() - start)
9
10 loc_sin = math.sin
11 start = time.time()
12 for i in range(10000000):
13 loc_sin(i)
14
15 print('# Time Used 2:',time.time() - start)
16
17 #我自己的方法
18 loc_sin = math.sin
19 start = time.time()
20 m = map(loc_sin,range(10000000))
21 l = list(m)
22 print('# Time Used 3:',time.time() - start)
23
24
25 # Time Used 1: 3.135605573654175
26 # Time Used 2: 2.6520047187805176
27 # Time Used 3: 2.979605197906494
虽然速度提高并不是非常多,但是对于某些对实时性要求特别高的应用场景一点点的提高也是有意义的。而实际上,上面这段代码还有优化的空间,大家能想到吗?本书其他地方介绍过,尝试着找一下。
《太极尺寸分毫解》曰“功夫先练开展,后练紧凑”。编写代码也是同样的道理,首先哟啊把代码写对,保证完全符合功能要求,然后再进行必要的优化来提高性能。过早地追求性能优化有时可能会带来灾难而浪费大量精力。代码优化牵涉的面非常广,对程序员的功底要求很高。除了上面介绍的循环代码优化,第2章和第5章中介绍的内容也涉及一些优化的内容。例如,如果经常需要测试一个序列是否包含一个元素应尽量使用字典或集合而不适用列表,连接多个字符串时尽量使用join()方法二不要使用运算符"+",对列表进行元素的插入和删除操作时应尽量从列表尾部进行,等等。
练习:1 输出“水仙花数”
所谓水仙花数是指一个3位的十进制数,其各位数字的立方和恰好等于该数本身,例如,153是水仙花数,因为 153 = 1 ** 3 + 5 ** 3 + 3 ** 3
1 import time
2
3 l = []
4
5 start = time.time()
6
7 for i in range(100,1000):
8 #获取个位数
9 g = i % 10
10
11 #获取十位数
12 s = i // 10 % 10
13
14 #获取百位数
15 b = i // 100
16
17 sxh = g ** 3 + s ** 3 + b ** 3
18 if sxh == i:
19 l.append(sxh)
20 print('# 程序执行耗时:',time.time() - start)
21 print('# 程序执行结果:{}'.format(l))
22
23 # 程序执行耗时: 0.015599966049194336
24 # 程序执行结果:[153, 370, 371, 407]