python代码优化技巧示例

Python代码优化

尽量使用内置函数

改进算法,选择合适的数据结构

字典和列表

  • Python 字典中使用了 hash table,因此查找操作的复杂度为 O(1),而 list 实际是个数组,在 list 中,查找需要遍历整个 list,其复杂度为 O(n),因此对成员的查找访问等操作字典要比 list 更快。
  • 对于列表,在CPython中,列表被实现为长度可变的数组。数组元素为对应对象的指针,增删改查的时间复杂度和C语言的类似。不好记得话可以联想C数组的相关操作过程。为了避免每次有新元素加入list时都要调用realloc进行内存分配,python会按一定的机制提前申请多的内存空间预留下来
操作时间复杂度
在尾部添加元素O(1)
在指定位置插入元素O(N)
获取元素O(1)
修改元素O(1)
删除元素O(N)
遍历O(N)
测试是否在列表中O(N)
min()/max()O(N)
获取列表长度O(1)
  • 对于字典,CPython使用伪随机探测(pseudo-random probing)的散列表(hash table)作为字典的底层数据结构。由于这个实现细节,只有可哈希的对象才能作为字典的键。Python中所有不可变的内置类型都是可哈希的。可变类型(如列表,字典和集合)就是不可哈希的,因此不能作为字典的键。

  • 字典的三个基本操作(添加元素,获取元素和删除元素)的平均事件复杂度为O(1)

  • 字典保存元素的顺序不是按照放入的顺序,如果要保持放入的顺序可以考虑collections模块提供了名为OrderedDicr的有序字典,collections.defaultdict不会引发‘KeyError’异常

  • 在遍历字典元素时,有一点需要特别注意。字典里的keys(), values()和items()3个方法的返回值不再是列表,而是视图对象(view objects)。

    • keys(): 返回dict_keys对象,可以查看字典所有键

    • values():返回dict_values对象,可以查看字典的所有值

    • items():返回dict_items对象,可以查看字典所有的{key, value}二元元组。

    • 这样的话就意味着,字典在遍历的时候,前面的操作可以影响后面的操作

       d = {1:2, 4:2}
       print(d)
       for k, v in d.items():
           if k == 1:
               d[4] = 3
           print(d)
       
       # 结果
       {1: 2, 4: 2}
       {1: 2, 4: 3}
       {1: 2, 4: 3}
      

集合和列表

  • set 的 union, intersection,difference 操作要比 list 的迭代要快。因此如果涉及到求 list 交集,并集或者差的问题可以转换为 set 来操作
  • CPython中集合和字典非常相似。事实上,集合被实现为带有空值的字典,只有键才是实际的集合元素。此外,集合还利用这种没有值的映射做了其它的优化。由于这一点,可以快速的向集合中添加元素、删除元素、检查元素是否存在。平均时间复杂度为O(1)

循环优化

对循环的优化所遵循的原则是尽量减少循环过程中的计算量,有多重循环的尽量将内层的计算提到上一层。 下面通过实例来对比循环优化后所带来的性能的提高。

from time import time


lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
listb = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.01]
t = time()
for i in range(1000000):
    for a in range(len(lista)):
        for b in range(len(listb)):
            x = lista[a] + listb[b]
print("total run time:", time() - t)

t = time()
len1 = len(lista)
len2 = len(listb)
for i in range(1000000):
    for a in range(len1):
        temp = lista[a]
        for b in range(len2):
            x = temp + listb[b]
print("total run time:", time() - t)

# total run time: 35.94349479675293
# total run time: 29.186986684799194

充分利用 Lazy if-evaluation 的特性

  • if A and B:当A为False时就不会判断B了
  • if A or B: 当A为True时就不会判断B了
from time import time


abbreviations = ['cf.', 'e.g.', 'ex.', 'etc.', 'fig.', 'i.e.', 'Mr.', 'vs.']
t = time()
for i in range(1000000):
    for w in ('Mr.', 'Hat', 'is', 'chasing', 'the', 'black', 'cat', '.'):
        if w in abbreviations:
            pass
print("total run time:", time() - t)

t = time()
for i in range(1000000):
    for w in ('Mr.', 'Hat', 'is', 'chasing', 'the', 'black', 'cat', '.'):
        if w[-1] == '.' and w in abbreviations:
            pass
print("total run time:", time() - t)

# total run time: 2.915415048599243
# total run time: 2.210136651992798

字符串优化

python 中的字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,因此这种持续的 copy 会在一定程度上影响 python 的性能。因此,如果要拼接 n 个字符串,会产生 n-1 个中间结果,每产生一个中间结果都需要申请和复制一次内存,严重影响运行效率。而使用 join() 拼接字符串时,会首先计算出需要申请的总的内存空间,然后一次性地申请所需内存,并将每个字符串元素复制到该内存中去。

  • 尽量使用join 而不是 + 连接字符串
  • 尽量使用内置函数来处理字符串
  • 对字符进行格式化( “C%s%s”)%(“A”, “B”))比直接串联(“C”+“A”+“B”)读取要快

使用列表解析(list comprehension)和生成器表达式(generator expression)

列表解析要比在循环中重新构建一个新的 list 更为高效, 差好多

from time import time

t = time()
list = ['a','b','is','python','jason','hello','hill','with','phone','test',
'dfdf','apple','pddf','ind','basic','none','baecr','var','bana','dd','wrd']
total=[]
for i in range (1000000):
    for w in list:
        total.append(w)
print("total run time:", time()-t)

t = time()
for i in range (1000000):
    total = [w for w in list]
print("total run time:", time()-t)
# total run time: 2.5990912914276123
# total run time: 0.8667190074920654

避免 .

避免模块和函数属性访问

每次使用.(属性访问操作符时)会触发特定的方法,如__getattribute__() 和__getattr__(),这些方法会进行字典操作,因此会带来额外的时间开销。通过 from import 语句,可以消除属性访问。

# 不推荐写法
import math

def computeSqrt(size: int):
    result = []
    for i in range(size):
        result.append(math.sqrt(i))
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

--------------------------------------------------------
# 推荐写法,第一次优化 . 模块属性访问
from math import sqrt

def computeSqrt(size: int):
    result = []
    for i in range(size):
        result.append(sqrt(i))  # 避免math.sqrt的使用
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

--------------------------------------------------------
# 推荐写法,第二次优化局部变量 sqrt,局部变量访问比全局变量快。
import math

def computeSqrt(size: int):
    result = []
    sqrt = math.sqrt  # 赋值给局部变量
    for i in range(size):
        result.append(sqrt(i))  # 避免math.sqrt的使用
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

--------------------------------------------------------
# 推荐写法, 第三次优化函数属性 list.append() 方法
import math

def computeSqrt(size: int):
    result = []
    append = result.append
    sqrt = math.sqrt    # 赋值给局部变量
    for i in range(size):
        append(sqrt(i))  # 避免 result.append 和 math.sqrt 的使用
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

避免访问类内属性

避免 . 的原则也适用于类内属性,访问 self._value 的速度会比访问一个局部变量更慢一些。通过将需要频繁访问的类内属性赋值给一个局部变量,可以提升代码运行速度。

# 将self._value 赋值给局部变量
import math
from typing import List

class DemoClass:
    def __init__(self, value: int):
        self._value = value
	
	# 推荐写法
    def computeSqrt1(self, size: int) -> List[float]:
        result = []
        append = result.append
        sqrt = math.sqrt
        value = self._value
        for _ in range(size):
            append(sqrt(value))  # 避免 self._value 的使用
        return result
    # 不推荐写法 
    def computeSqrt1(self, size: int) -> List[float]:
        result = []
        append = result.append
        sqrt = math.sqrt
        for _ in range(size):
            append(sqrt(self._value))  
        return result

def main():
    size = 10000
    for _ in range(size):
        demo_instance = DemoClass(size)
        demo_instance.computeSqrt1(size)
        demo_instance.computeSqrt2(size)

main()

其他优化技巧

  • 如果需要交换两个变量的值使用 a,b=b,a (更快)而不是借助中间变量 t=a;a=b;b=t;
  • 使用局部变量,避免"global" 关键字。
  • if done is not None 比语句 if done != None 更快。
  • 使用profile模块定位瓶颈代码位置
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值