猛戳加速键:你拥有这些Python加速技能吗?

开发 后端
但其实很多时候,Python的效率并没有达到它应有的速度,有一些让它马达开足的小技巧,一起来学习吧!

本文转载自公众号“读芯术”(ID:AI_Discovery)。

Python有时用起来确实很慢,我敢打赌你肯定抱怨过这一点,尤其是那些用惯了C,C ++或Java的人。

但其实很多时候,Python的效率并没有达到它应有的速度,有一些让它马达开足的小技巧,一起来学习吧!

[[331840]]

1. 避免使用全局变量

import mathsize = 10000 
for x in range(size): 
    for y in range(size): 
        z = math.sqrt(x) + math.sqrt(y) 
  • 1.
  • 2.
  • 3.
  • 4.

许多程序员一开始都会用Python语言编写一些简单的脚本。编写脚本时,通常直接使用全局变量,就像上面这段代码。

但由于全局变量和局部变量的实现方式不同,全局变量中定义的代码要比在函数中定义的函数运行起来慢得多。把脚本语句放入函数中,通常运行速度可提高15%-30%。如下所示:

import mathdef main(): 
    size = 10000 
    for x in range(size): 
        for y in range(size): 
            z = math.sqrt(x) +math.sqrt(y)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

2. 避免数据重复

避免无意义的数据复制

def main(): 
    size = 10000 
    for _ in range(size): 
        value = range(size) 
        value_list = [x for x in value] 
        square_list = [x * x for x invalue_list]main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

这段代码中,value_list完全没有必要,这会创建不必要的数据结构或复制。

def main(): 
    size = 10000 
    for _ in range(size): 
        value = range(size) 
        square_list = [x * x for x invalue]main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

另一个原因在于Python的数据共享机制过于偏执,没有很好理解或信任内存模型,例如滥用copy.deepcopy()函数。我们可以删除此类代码中的复制操作。

交换值时无需使用中间变量

def main(): 
    size = 1000000 
    for _ in range(size): 
        a = 3 
        b = 5 
        temp = a 
        a = b 
        b = tempmain() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

上述代码在交换值时创建了一个临时变量temp。如果没有中间变量,代码会更加简洁,运行速度也更快。

def main(): 
    size = 1000000 
    for _ in range(size): 
        a = 3 
        b = 5 
        a, bb = b, amain() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

使用字符串联方法join ,而不是'+'

import string 
from typing import Listdef concatString(string_list: List[str]) -> str: 
    result = '' 
    for str_i in string_list: 
        result += str_i 
    return resultdef main(): 
    string_list =list(string.ascii_letters * 100) 
    for _ in range(10000): 
        result =concatString(string_list)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

另一要点是a+b对字符串进行拼接,由于在Python中字符串是不可变的对象,所以实际上a和b分别复制到了应用程序的新内存空间中。

因此,如果拼接n个字符串会产生“ n-1”个中间结果,则每个字符串都会产生应用和复制内存所需的中间结果,从而严重影响操作效率。

在使用join()串联字符串时,首先计算需要应用的总内存空间,然后立即申请所需的内存,再把每个字符串元素复制到内存中。

import string 
from typing import Listdef concatString(string_list: List[str]) -> str: 
    return ''.join(string_list)defmain(): 
    string_list = list(string.ascii_letters* 100) 
    for _ in range(10000): 
        result =concatString(string_list)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

3. 避免使用以下函数属性

避免访问模块和函数属性

import mathdef computeSqrt(size:int): 
    result = [] 
    for i in range(size): 
        result.append(math.sqrt(i)) 
    return resultdef main(): 
    size = 10000 
    for _ in range(size): 
        result = computeSqrt(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

use(属性访问运算符)会触发特定方法,例如getattribute()和getattr(),这些方法将执行字典操作,会产生额外的时间消耗。

通过使用import语句,可以消除属性访问:

from math import sqrtdefcomputeSqrt(size: int): 
    result = [] 
    for i in range(size): 
        result.append(sqrt(i)) 
    return resultdef main(): 
    size = 10000 
    for _ in range(size): 
        result = computeSqrt(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

前文中我们讨论了局部变量可以比全局变量实现更快查找,对于经常访问的变量(如sqrt),可以通过更改为局部变量以加快操作速度。

import mathdef computeSqrt(size:int): 
    result = [] 
    sqrt = math.sqrt 
    for i in range(size): 
        result.append(sqrt(i)) 
    return resultdef main(): 
    size = 10000 
    for _ in range(size): 
        result = computeSqrt(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

避免类属性访问

import math 
from typing import Listclass DemoClass: 
    def __init__(self, value: int): 
        self._value = value 
    
    def computeSqrt(self, size: int)-> List[float]: 
        result = [] 
        append = result.append 
        sqrt = math.sqrt 
        for _ in range(size): 
            append(sqrt(self._value)) 
        return resultdef main(): 
    size = 10000 
    for _ in range(size): 
        demo_instance = DemoClass(size) 
        result =demo_instance.computeSqrt(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

避免的原理也适用于类的属性,并且访问self._value的速度要比访问局部变量的速度要慢。通过把需要频繁访问的类属性分配给局部变量,可以提高代码执行速度。

import math 
from typing import Listclass DemoClass: 
    def __init__(self, value: int): 
        self._value = value 
    
    def computeSqrt(self, size: int)-> List[float]: 
        result = [] 
        append = result.append 
        sqrt = math.sqrt 
        value = self._value 
        for _ in range(size): 
            append(sqrt(value)) 
        return resultdef main(): 
    size = 10000 
    for _ in range(size): 
        demo_instance = DemoClass(size) 
       demo_instance.computeSqrt(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

4. 避免不必要的抽象

class DemoClass: 
    def __init__(self, value: int): 
        self.value = value@property 
    def value(self) -> int: 
        return self._value@value.setter 
    def value(self, x: int): 
        self._value = xdef main(): 
    size = 1000000 
    for i in range(size): 
        demo_instance = DemoClass(size) 
        value = demo_instance.value 
        demo_instance.value = imain() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

每当使用其他处理层(例如装饰器、属性访问、描述符)封装代码时,代码运行的速度也会变慢。在大多数情况下,重新检查是否有必要使用属性访问器定义是很有必要的。

使用getter/setter函数访问属性通常是被C/C++程序员遗忘的一种编码样式。如果确实没有必要,就使用简单属性就好。

class DemoClass: 
    def __init__(self, value: int): 
        self.value = valuedef main(): 
    size = 1000000 
    for i in range(size): 
        demo_instance = DemoClass(size) 
        value = demo_instance.value 
        demo_instance.value = imain() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

5. 选择合适的数据结构

众所周知,列表是Python中的动态数组。当预分配的内存空间用完时,会预分配一定的内存空间,然后继续向其中添加元素。然后复制之前的所有原始元素,形成一个新的内存空间,在插入新元素之前销毁先前的内存空间。

因此,如果频繁添加或删除,或者添加或删除的元素数量太大,列表的效率就会变低,目前最好使用collections.deque。

此双端队列具有堆栈和队列的特性,并且可以在两端以O(1)复杂度执行插入和删除操作。

列表搜索操作非常耗时。当需要频繁查找某些元素或按顺序频繁访问这些元素时,保持列表 对象有序的情况下使用二分法,使用二进制搜索以提高搜索效率,但二进制搜索仅适用于有序元素。

另一个常见的要求是找到最小值或最大值。此时,可以使用heapq模块列出转换为堆的列表,因此获取最小值的时间复杂度为O(1)。

6. 循环优化

使用 for 循环代替while 循环

def computeSum(size: int) ->int: 
    sum_ = 0 
    i = 0 
    while i < size: 
        sum_ += i 
        i += 1 
    return sum_def main(): 
    size = 10000 
    for _ in range(size): 
        sum_ = computeSum(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

要知道,Python中的for循环要比while循环快得多。

def computeSum(size: int) ->int: 
    sum_ = 0 
    for i in range(size): 
        sum_ += i 
    return sum_def main(): 
    size = 10000 
    for _ in range(size): 
        sum_ = computeSum(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

使用隐式for循环,而不是显式for循环

对于上面的示例,可以进一步使用隐式for循环替换显式for循环

def computeSum(size: int) ->int: 
    return sum(range(size))def main(): 
    size = 10000 
    for _ in range(size): 
        sum = computeSum(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

减少内部循环的计算

from math import sqrtdef main(): 
    size = 10000    for x in range(size): 
        for y in range(size): 
            z = sqrt(x) + sqrt(y)main() 
  • 1.
  • 2.
  • 3.
  • 4.

在上述for循环中的代码sqrt(x)中,在训练期间每次都需要进行重新计算,这会增加时间消耗。

import mathdef main(): 
    size = 10000for x in range(size): 
        sqrtsqrt_x = sqrt(x) 
        for y in range(size): 
            z = sqrt_x + sqrt(y)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

7. 使用 numba.jit

继续遵循上述示例,并在此基础上使用numba.jit。Python函数JIT可以编译为机器代码用以执行,这能大大提高了代码执行速度。

import numba@numba.jit 
def computeSum(size: float) -> int: 
    sum = 0 
    for i in range(size): 
        sum += i 
    return sumdef main(): 
    size = 10000 
    for _ in range(size): 
        sum = computeSum(size)main() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

8. 代码优化原则

上文已经介绍了许多加速Python代码的技术。在编写代码的过程中,我们需要了解代码优化的一些基本原理,这可是“实用知识”。

第一个基本原则就是不要过早优化代码。

许多人一开始编写代码时就致力于性能优化,“加快正确程序的速度要比确保快速程序的正确运作容易得多。”优化代码的前提是确保代码可以正常工作。过早的优化可能会忽略对总体性能指标的掌握,并且在获得总体结果之前不要颠倒顺序。

第二个基本原则是权衡优化代码的成本。

优化代码是有代价的,想要解决所有性能问题几乎不可能。通常面临的选择是时间换空间或空间换时间,还需要考虑开发成本。

第三个原则是不要优化无关紧要的部分。

如果优化代码的每个部分后,这些变更会让代码变得难以阅读和理解。如果代码运行缓慢,首先必须找到代码运行缓慢的位置(通常是内部循环),重点优化代码运行缓慢的地方。对于其他位置,时间的损失影响很小。

优化代码,让你的Python开足马力,快去实践一下吧!

 

责任编辑:赵宁宁 来源: 今日头条
相关推荐

2019-03-25 21:18:41

数据科学家大数据技能

2013-03-25 09:41:20

PythonCython

2020-07-23 14:15:42

Cython的Python代码

2024-12-03 00:38:37

数据湖存储COS

2020-06-15 14:43:16

Python开发工具

2009-06-29 10:34:49

网站加速Page SpeedGoogle

2021-07-05 09:40:57

工具Node开源

2021-03-18 07:52:42

代码性能技巧开发

2020-06-22 15:41:20

IF函数Excel用法

2017-10-18 09:49:57

ERP信息化CIO

2024-08-09 11:07:46

缓存检索filter

2018-03-19 08:30:02

编程开发技能

2022-05-29 08:54:44

Edge浏览器

2011-01-13 14:38:00

JavascriptCSSWeb

2020-05-25 18:42:52

Windows 10Windows操作系统

2018-08-14 08:14:27

安卓Google特性

2019-10-17 10:30:34

Python脚本语言设计

2023-06-08 11:23:56

数字化转型企业

2022-08-01 08:12:14

位运算代码性能

2019-10-17 09:57:08

Python设计电脑
点赞
收藏

51CTO技术栈公众号