Python通用编程规范-07 性能

7.1 List容量初始化

在list成员个数可以预知的情况下,创建list时需预留空间正好容纳所有成员的空间

说明:与JavaC++等语言的list一样,Python语言的listappend()成员时,如果没有多余的空间容纳新的成员,就会分配一块更大的内存,并将原来内存里的成员拷贝到新的内存上,并将最新append()的成员也拷贝到此新内存空间中,然后释放老的内存空间。如果append()调用次数很大,则如上过程会频繁发生,因而会造成灾难性性能下降,而不仅仅是一点下降。

# 错误示例:
members = []
for i in range(1, 1000000):
    members.append(i)
    len(members)
# 正确示例:
members = [None] * 1000000
for i in range(1, 1000000):
    members[i] = i
    len(members)

7.2 元素个数确定时推荐使用Tuple

在成员个数及内容皆不变的场景下尽量使用tuple替代list

说明:list是动态array,而tuple是静态array(其成员个数以及内容皆不可变)。因此,list需要更多的内存来跟踪其成员的状态。
此外,对于成员个数小于等于20的tuplePython会对其进行缓存,即当此tuple不再使用时,Python并不会立即将其占用的内存返还给操作系统,而是保留以备后用。

# 错误示例:
myenum = [1, 2, 3, 4, 5]
# 正确示例:
# 如果恰好被缓存过,则初始化速度会为错误示例中的5倍以上。
myenum = (1, 2, 3, 4, 5)

7.3 推荐使用局部变量引用频繁使用的外界对象

对于频繁使用的外界对象,尽量使用局部变量来引用之

说明:在Python中对一个函数、变量、模块的调用,是以一种字典树的方式来查找的。Python首先会查找locals()数组,这里保存着所有的局部变量;如果找不到,则会继续查找globals()数组;如果在这里也找不到,则会到buildtin(其实是一个模块)中的locals()数组中查找,或者到其它import进来的模块/类中查找。

# 错误示例:
import math
def afunc():
    for x in range(100000):
        return math.tan(x)
# 在这个例子中,Python会先到 globals()中的名值对字典中,找到math模块;
# 然后在math模块的 locals() 的字典中查找 tan() 函数;
# 然后在当前函数的 locals() 中查找 x。这里存在着3次查找。
# 当调用次数大时,每次调用多出来的1、2次查找,就会被放大。

# 错误示例:
from math import tan
def afunc():
    for x in range(100000):
        return tan(x)
# 在这个例子中,Python会先到 globals() 的字典中查找tan()函数(其已经被from math import tan语句加载到了globals()中);
# 然后在当前函数的locals()中查找x。这里存在着2次查找,比前一个例子少了一次查找,但是还不是最优解。

# 正确示例:
import math
def afunc(tan=math.tan):
    for x in range(100000):
        return tan(x)

在这个例子中,在函数定义时,有且只有一次查找math模块、然后查找tan函数的操作;之后在循环中对tan()函数的调用,都是在afunc()函数的locals()中进行查找,而对函数的locals()中的查找,Python是有特殊优化措施的,速度是非常快的;当然,还包括对本地变量x的查找(也是在当前函数的locals()中查找)。

7.4 尽量使用generator comprehension代替listcomprehension

说明:list comprehension可以用来代替lambda表达式的mapreduce语法,从已有的list中,生成新的数据。而generator comprehension无需定义一个包含yield语句的函数,就可以生成一个generator。 二者一个生成list,另外一个生成generator,在内存的占用上,相差悬殊;在生成速度上,相差无几。

# 错误示例:
even_cnt = len([x for x in range(10) if x % 2 == 0])
# 正确示例:
even_cnt = sum(1 for x in range(10) if x % 2 == 0)

7.5 使用字符串格式化方式代替"+“和”+="操作符

使用format方法、"%“操作符和join方法代替”+“和”+="操作符来完成字符串格式化

说明:即使参数都是字符串,也可以使用format方法或%运算符来格式化字符串。一般性能要求的场景可以使用++=运算符,但需要避免使用++=运算符在循环中累积字符串。由于字符串是不可变的,因此会产生不必要的临时对象并导致二次而非线性运行时间。

# 推荐做法:
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}!'.format(imperative, expletive)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
items = ['<table>']
for last_name, first_name in employee_list:
    items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
    items.append('</table>')
    employee_table = ''.join(items)

# 不推荐做法:
x = imperative + ', ' + expletive + '!'
x = 'name: ' + name + '; score: ' + str(n)
employee_table = '<table>'
for last_name, first_name in employee_list:
    employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
    employee_table += '</table>'

版权声明:本文为zhao12501原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。