1.函数返回值缓存

可以把一个非常耗时的函数调用变成O(1)时间复杂度 适用于在处理固定参数的函数被重复调用时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import math 
import time 
import random

class Memoized:
    def __init__(self, fn):
        self.fn = fn 
        self.results = {}
    def __call__(self, *args):
        key = ''.join(map(str, args[0])) 
        try:
            return self.results[key] 
        except KeyError:
            self.results[key] = self.fn(*args)
            return self.results[key]

@Memoized
def twoParamsMemoized(values, period):
     totalSum = 0 for x in range(0, 100):
     for v in values:
         totalSum = math.pow((math.sqrt(v) * period), 4) + totalSum 
     return totalSum

2.用字典做查询表

用字典做查询表 > 复杂度 O(1) 列表或链表做查询表 > 复杂度 O(n)

trig_lookup_table = defaultdict(lambda: 0)

3.使用默认参数

默认参数(default argument)可以在函数创建时就确定输入值,而不用在运行阶段才确定输入 这种方法只能用于在运行过程中参数不发生变化的函数和对象

1
2
3
4
5
6
7
import time import math
def degree_sin(deg):
    return math.sin(deg * math.pi / 180.0) * math.cos(deg * math.pi / 180.0)
   
def degree_sin_opt(deg, factor=math.pi/180.0, sin=math.sin, cos=math.cos):
    return sin(deg * factor) * cos(deg * factor)

4.列表生成式与生成器

4.1 列表生成式和for循环

1
2
3
4
5
6
multiples_of_two = [x for x in range(100) if x % 2 == 0]

multiples_of_two = []
for x in range(100):
    if x % 2 == 0:
        multiples_of_two.append(x)

列表生成式比for循环的性能更好

for循环产生的指令集更长 在for循环里,数值是一个一个增加的,用到三个指令(LOAD_ATTR、LOAD_NAME和CALL_FUNCTION) 列表综合只用了一个简单且已经经过优化的指令(LIST_APPEND)

即使for循环需要做额外操作(会产生副作用),也切记不要肆意将所有的for循环都改成列表综合。 这是因为有时候未经优化的列表综合,可能比for循环消耗的时间更长

4.2 生成器

1
2
3
4
5
my_list = (x**2 for x in range(100)) 
# 你不能这么写 print my_list[1] 
# 但是可以这么写 
for number in my_list:
    print number

生成器只能遍历一次,列表对象可以遍历任意次

综合: 生成器表达式在创建规模较大的列表时更有优势,而创建规模较短的列表时,列表综合表达式更有优势

5.ctypes

5.1 把关键代码写成C语言,编译成一个库,然后导入Python当作模块使用 5.2 加载一个系统库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 生成随机数
import time 
import random 
from ctypes import cdll

libc = cdll.LoadLibrary('libc.so.6') # Linux系统
#libc = cdll.msvcrt # Windows系统

init = time.clock() 
# 花费0.9秒
randoms = [random.randrange(1, 100) for x in xrange(1000000)] 
print "Pure python: %s seconds" % (time.clock() - init)

# 花费0.3秒
init = time.clock() 
randoms = [(libc.rand() % 100) for x in xrange(1000000)] 
print "C version : %s seconds" % (time.clock() - init)

6.字符串连接

字符串不可变, 每次对字符串的连接/替换 都会创建新的字符串

1
2
3
4
5
6
7
# more effect
full_doc = "".join(world_list)

# less effect
document = title + introduction + main_piece + conclusion
# more effect
document = "%s%s%s%s" % (title, introduction, main_piece, conclusion)

7.其他

7.1 成员关系测试 当我们想判断一个值是否在一个列表(list)中时 比如像“a in b”这样的操作,用集合(set) 或字典(dict)(查找时间复杂度是O(1) 可以获得比列表(list)和元组(tuple)更好的性能

7.2 不要重复发明轮子 Python的标准库核心组件大都是用经过优化的C语言写成的。因此不需要你自建,而且你自建的很可能会更慢。像列表、元组、集合和字典这些数据类型, 以及数组(array)、迭代工具(itertools)和队列(collections.deque)这些模 块都推荐使用。 使用内置函数, 如 map(operator.add, list1, list2), 也会比 map(lambda x, y: x+y, list1, list2)更快

7.3 不要忘记了队列 当需要一个固定长度的数组或可变长度的栈(stack)时,列表非常适合。 但是,在处理pop(0)或insert(0, your_list)操作时,可以试试collections. deque,因为它在列表的任何一端都可以快速地完成(O(1))插入和弹出操作

7.4 有时不定义函数更好 调用函数会增加大量的资源消耗 有时候,尤其是在时间密集的循环体中内联函数代码,不调用外部函数,可以更加高效 这个窍门有一个很大的代价,因为它可能会损害代码的可读性和维护便利性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import time

def fn(nmbr):
     return (nmbr ** nmbr) // (nmbr + 1)

nmbr = 0
init = time.clock()
for i in range(1000):
    fn(i)
    
# 0.013s
print("Total time: %s" % (time.clock() - init))
init = time.clock()
nmbr = 0

for i in range(1000):
     nmbr = (nmbr ** nmbr) // (nmbr + 1)
     
# 0.00041s
print("Total time (inline): %s" % (time.clock() - init))

7.5 尽可能用key函数排序 在对列表进行自定义规则的排序时,不要用比较函数排序,而是 应该尽可能地用key函数排序。 这是因为每个元素只需要调用一次key函数,而在运行过 程中每一项都要调用比较函数好几次

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import random import time
# 创建两个随机数组 
list1 = [[random.randrange(0, 100), chr(random.randrange(32, 122))] for x in range(100000)] 
list2 = [[random.randrange(0, 100), chr(random.randrange(32, 122))] for x in 
range(100000)]

# 通过比较函数cmp()对两个数据排序 

init = time.clock()
list1.sort(cmp=lambda a, b: cmp(a[1], b[1])) 

print "Sort by comp: %s" % (time.clock() - init) # 打印结果0.213434
# 把字符串元素作为词典的键,进行排序 
init = time.clock() 
list2.sort(key=lambda a: a[1]) 
print "Sort by key: %s" % (time.clock() - init) # 打印结果0.047623

7.6 1比True好 while 1比while True要好 7.7 多元赋值 多元赋值(a,b = “hellothere”, 123)通常比单独赋值要慢。但是,在进行变量交换时,它比普通方法要快(因为我们不需要 使用临时变量和赋值过程)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  a = "hello world" 
  b = 123 # 这种做更快
  
  a, b = b, a # 相比下面这种方式要快
  tmp = a
  a = b
  b = tmp
  ```
  
7.8 推荐使用链式比较
在比较三个变量时,不要用x<y和y<z,可以用x<y<z。这样更容易阅读(更自然)且运行更快

7.9 用命名元组(namedtuple)替换常规对象
使用常规的类(class)方法创建存储数据的简单对象时,实例中会有一个字典存储属性。这个存储对属性少的对象来说是一种浪费。 
如果你需要创建大量的简单对象,会浪费大量内存。这种情况下,你可以用命名元组。这是一个新的tuple子类,可以轻松地构建并优化任务

```py
import time 
import collections

class Obj(object):
  def __init__(self, i):
      self.i = i 
      self.l = []

all = {} 
init = time.clock() 
for i in range(1000000):
  all[i] = Obj(i) 

# 打印Regular对象:2.384832 
print "Regular Objects: %s" % (time.clock() - init)

Obj = collections.namedtuple('Obj', 'i l')
all = {} 
init = time.clock() 
for i in range(1000000):
  all[i] = Obj(i, []) 
# 打印NamedTuples对象:1.272023 
print "NamedTuples Objects: %s" % (time.clock() - init)      

from 《Python性能分析与优化》