本文转载自公众号《核心阅读》(ID:AI_Discovery)。Python有时使用起来真的很慢,我敢打赌你已经抱怨过这一点,特别是如果你习惯了C、C++或Java。但实际上,在很多情况下,Python的效率并没有达到应有的速度。有一些小技巧可以让它的马达全开,一起来学习吧!1、避免使用全局变量importmathsize=10000forxinrange(size):foryinrange(size):z=math.sqrt(x)+math.sqrt(y)很多程序员都是从用Python语言写一些简单的脚本开始的。在编写脚本时,直接使用全局变量是很常见的,如上面的代码。但是由于全局变量和局部变量的实现方式不同,定义在全局变量中的代码运行起来要比定义在函数中的函数慢很多。将脚本语句放入函数中通常可以提高运行速度15%-30%。如下图:importmathdefmain():size=10000forxinrange(size):foryinrange(size):z=math.sqrt(x)+math.sqrt(y)main()2.避免数据重复避免无意义的数据重复defmain():size=10000for_inrange(size):value=range(size)value_list=[xforxinvalue]square_list=[x*xforxinvalue_list]main()在这段代码中,value_list是完全不需要的,会创建不必要的数据结构或者拷贝。defmain():size=10000for_inrange(size):value=range(size)square_list=[x*xforxinvalue]main()另一个原因是Python的数据共享机制太偏执,内存模型不太好理解或信任,比如滥用copy.deepcopy()函数。我们可以去掉这样代码中的复制操作。交换值时不需要使用中间变量defmain():size=1000000for_inrange(size):a=3b=5temp=aa=bb=tempmain()上面的代码在交换值时创建了一个临时变量temp。没有中间变量,代码更干净,运行速度更快。defmain():size=1000000for_inrange(size):a=3b=5a,bb=b,amain()使用字符串拼接方式join代替'+'importstringfromtypingimportListdefconcatString(string_list:List[str])->str:result=''forstr_iinstring_list:result+=str_ireturnresultdefmain():string_list=list(string.ascii_letters*100)for_inrange(10000):result=concatString(string_list)main()还有一点就是a+b拼接字符串,因为在Python中String是不可变的对象,所以a和b实际上被复制到应用程序的新内存空间中。因此,如果拼接n个字符串产生“n-1”个中间结果,每个字符串产生申请和复制内存所需的中间结果,严重影响运行效率。使用join()拼接字符串时,先计算需要申请的总内存空间,然后立即申请需要的内存,然后将每个字符串元素复制到内存中。importstringfromtypingimportListdefconcatString(string_list:List[str])->str:return''.join(string_list)defmain():string_list=list(string.ascii_letters*100)for_inrange(10000):result=concatString(string_list)main()3.避免使用以下函数属性避免访问模块和函数属性importmathdefcomputeSqrt(size:int):result=[]foriinrange(size):result.append(math.sqrt(i))returnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)main()use(attributeaccessoperator)会触发特定的方法,比如getattribute()和getattr(),会进行字典操作,会产生额外的时间消耗。可以使用导入语句消除属性访问:frommathimportsqrtdefcomputeSqrt(size:int):result=[]foriinrange(size):result.append(sqrt(i))returnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)main()在上一篇文章中,我们讨论了可以比全局变量更快地搜索局部变量。对于经常访问的变量(如sqrt),可以通过将其改为局部变量来加快运行速度。导入mathdefcomputeSqrt(size:int):result=[]sqrt=math.sqrtforiinrange(size):result.append(sqrt(i))returnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)main()避免类属性访问:append(sqrt(self._value))returnresultdefmain():size=10000for_inrange(size):demo_instance=DemoClass(size)result=demo_instance.computeSqrt(size)main()回避原则同样适用于类属性,访问self._value比访问局部变量慢。可以通过将频繁访问的类属性分配给局部变量来提高代码执行速度。importmathfromtypingimportListclassDemoClass:def__init__(self,value:int):self._value=valuedefcomputeSqrt(self,size:int)->List[float]:result=[]append=result.appendsqrt=math.sqrtvalue=self._valuefor_inrange(size):append(sqrt(value))returnresultdefmain():size=10000for_inrange(size):demo_instance=DemoClass(size)demo_instance.computeSqrt(size)main()4.避免不必要的抽象类DemoClass:def__init__(self,value:int):self.value=value@propertydefvalue(self)->int:returnsself._value@value.setterdefvalue(self,x:int):self._value=xdefmain():size=1000000foriinrange(size):demo_instance=DemoClass(size)value=demo_instance.valuedemo_instance.value=imain()每当代码被额外的处理层(例如装饰器、属性访问、描述符)包裹时,代码也会运行得更慢。在大多数情况下,值得重新检查属性访问器定义是否必要。使用getter/setter函数访问属性是一种经常被C/C++程序员遗忘的编码风格。如果您真的不需要,只需使用简单的属性。classDemoClass:def__init__(self,value:int):self.value=valuedefmain():size=1000000foriinrange(size):demo_instance=DemoClass(size)value=demo_instance.valuedemo_instance.value=imain()5.选择合适的数据结构众所周知,列表在Python中是动态数组。当预分配的内存空间用完后,会预分配一定的内存空间,然后继续往里面添加元素。然后将之前的原始元素全部复制,形成一个新的内存空间,在插入新元素之前先销毁之前的内存空间。因此,如果频繁增删改查,或者增删元素数量过多,列表的效率就会变低,目前最好使用collections.deque。这个双端队列具有栈和队列的特性,可以在两端进行复杂度为O(1)的插入和删除操作。列表搜索操作非常耗时。当需要频繁查找某些元素或频繁按顺序访问这些元素时,在保持列表对象有序的情况下使用二分法,并使用二分查找来提高查找效率,但二分查找仅适用于有序元素。另一个常见的要求是找到最小值或最大值。此时可以使用heapq模块将链表转成堆,所以求最小值的时间复杂度为O(1)。6.for循环优化使用for循环代替while循环defcomputeSum(size:int)->int:sum_=0i=0whiei
