Python是一种脚本语言。与C/C++等编译型语言相比,它在效率和性能方面存在一些不足。然而,在很多情况下,Python的效率并没有想象中那么夸张。本文整理了一些加速Python代码运行的技巧。0.代码优化原则本文将介绍很多加速Python代码运行的技巧。在深入了解代码优化的细节之前,您需要了解一些代码优化的基本原则。第一个基本原则是不要过早优化。许多人开始以性能优化为目标编写代码,“使正确的程序更快比使快速的程序正确更容易”。因此,优化的前提是代码能正常运行。过早的优化可能忽略了对整体性能指标的把握。在得到整体结果之前,不要颠倒优先级。第二个基本原则是权衡优化成本。优化是有代价的,几乎不可能解决所有的性能问题。通常面临的选择是时间换空间还是空间换时间。此外,还需要考虑开发成本。第三个原则是不要优化那些无关紧要的部分。如果代码的每个部分都被取消优化,这些修改会使代码难以阅读和理解。如果你的代码跑慢了,首先要找到代码跑慢的地方,一般是内循环,着重优化跑慢的地方。在其他地方,稍微浪费一点时间也没什么大不了的。1.避免使用全局变量#不推荐。代码耗时:26.8秒在编写脚本的时候,通常是直接写成一个全局变量,比如上面的代码。但是,由于全局变量和局部变量的实现方式不同,定义在全局范围内的代码运行起来会比定义在函数内的代码慢很多。通过将脚本语句放入函数中,您通常可以看到15%-30%的加速。#推荐写法。代码耗时:20.6秒importmathdefmain():#定义到函数中减少所有变量使用size=10000forxinrange(size):foryinrange(size):z=math.sqrt(x)+math.sqrt(y)main()2。避免模??块和函数属性访问#Notrecommended.代码耗时:14.5秒importmathdefcomputeSqrt(size:int):result=[]foriinrange(size):result.append(math.sqrt(i))returnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)每次main()使用.(属性访问运算符),它会触发特定的方法,比如__getattribute__()和__getattr__(),这些方法会进行字典操作,从而带来额外的时间开销。使用fromimport语句,可以消除属性访问。#首次优化写法。代码耗时:10.9秒frommathimportsqrtdefcomputeSqrt(size:int):result=[]foriinrange(size):result.append(sqrt(i))#避免使用math.sqrtreturnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)main()在第1节中,我们提到局部变量比全局变量查找速度更快,所以对于频繁访问的变量sqrt,将其改为局部变量可以加快运行速度。#写法的第二次优化。代码耗时:9.9秒importmathdefcomputeSqrt(size:int):result=[]sqrt=math.sqrt#赋值给局部变量foriinrange(size):result.append(sqrt(i))#避免使用math.sqrtreturnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)main()除了math.sqrt,还有.在computeSqrt函数中,就是调用list的append方法。通过将此方法分配给局部变量,使用.computeSqrt函数中的for循环内部可以完全消除。#推荐写法。代码耗时:7.9秒mathUseof.sqrtreturnresultdefmain():size=10000for_inrange(size):result=computeSqrt(size)main()3.避免类内属性访问#不推荐。代码耗时:10.4秒(size):append(sqrt(self._value))returnresultdefmain():size=10000for_inrange(size):demo_instance=DemoClass(size)result=demo_instance.computeSqrt(size)main()规避原则。也适用于类内属性,访问self._value将比访问局部变量慢。通过将频繁访问的类内属性分配给局部变量,可以提高代码执行速度。#推荐写法。代码耗时:8.0秒=self._valuefor_inrange(size):append(sqrt(value))#避免使用self._valuereturnresultdefmain():size=10000for_inrange(size):demo_instance=DemoClass(size)demo_instance.computeSqrt(size)main()4.避免不必要的抽象#不推荐,代码耗时:0.55秒:int):self._value=xdefmain():size=1000000foriinrange(size):demo_instance=DemoClass(size)value=demo_instance.valuedemo_instance.value=imain()任何时候你使用额外的处理层(比如装饰器,属性访问,descriptors)来包装代码,它会使代码变慢。在大多数情况下,需要重新检查是否有必要使用属性访问器的定义。使用getter/setter函数访问属性通常是C/C++程序员遗留下来的代码风格。如果确实不需要,请使用简单的属性。#推荐写法,代码耗时:0.33秒classDemoClass:def__init__(self,value:int):self.value=value#避免不必要的属性访问器defmain():size=1000000foriinrange(size):demo_instance=DemoClass(size)value=demo_instance.valuedemo_instance.value=imain()5.避免数据拷贝4.1避免无意义的数据拷贝#不推荐,代码耗时:6.5秒defmain():size=10000for_inrange(size):value=range(size)value_list=[xforxinvalue]square_list=[x*xforxinvalue_list]上面代码中main()中的value_list是完全没有必要的,会产生不必要的数据结构或副本。#推荐写法,代码耗时:4.8秒defmain():size=10000for_inrange(size):value=range(size)square_list=[x*xforxinvalue]#避免无意义复制main()另一种情况是针对Python的数据共享机制过于偏执,不理解也不信任Python的内存模型,滥用copy.deepcopy()等函数。通常在这些代码中可以删除复制操作。5.2交换值时不要使用中间变量#不推荐,代码耗时:0.07秒defmain():size=1000000for_inrange(size):a=3b=5temp=aa=bb=tempmain()以上代码为交换值创建一个临时变量temp。没有中间变量的帮助,代码更简洁,运行速度更快。#推荐写法,代码耗时:0.06秒defmain():size=1000000for_inrange(size):a=3b=5a,bb=b,a#不用中间变量main()5.3字符串拼接使用join而不是+#否推荐写法,代码耗时:2.6秒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中的字符串是不可变对象,所以会申请一块内存空间,将a和b复制到新申请的内存空间中。因此,如果要拼接n个字符串,会产生n-1个中间结果,每个中间结果都需要分配和复制一次内存,严重影响运行效率。使用join()拼接字符串时,会先计算需要申请的内存空间总量,然后一次性申请所需的内存,将每一个字符串元素复制到内存中。#推荐写法,代码耗时:0.3秒100)for_inrange(10000):result=concatString(string_list)main()6。使用if条件的短路特性#不推荐,代码耗时:0.05秒fromtypingimportListdefconcatString(string_list:List[str])->str:abbreviations={'cf.','e.g.','ex.','etc.','flg.','i.e.','Mr.','vs.'}abbr_count=0result=''forstr_iinstring_list:ifstr_inabbreviations:result+=str_ireturnresultdefmain():for_inrange(10000):string_list=['Mr.','Hat','is','Chasing','the','black','cat','.']result=concatString(string_list)main()if条件的短路特性表示对于ifa,b等语句,当a为False时,直接返回,不计算b;对于ifaorb等语句,当a为True时,它??会直接返回Return,而不评估b。因此,为了节省运行时间,对于or语句,应将True概率较大的变量写在or之前,而and应推迟。#推荐写法,代码耗时:0.03秒fromtypingimportListdefconcatString(string_list:List[str])->str:abbreviations={'cf.','e.g.','ex.','etc.','flg.','i.e.','Mr.','vs.'}abbr_count=0result=''forstr_iinstring_list:ifstr_i[-1]=='.'andstr_iinabbreviations:#利用if条件的短路特性result+=str_ireturnresultdefmain():for_inrange(10000):string_list=['Mr.','Hat','is','Chasing','the','black','cat','.']result=concatString(string_list)main()7.循环优化7.1用for循环代替while循环#不推荐写法。代码耗时:6.7秒defcomputeSum(size:int)->int:sum_=0i=0while
