棉花糖不甜 发表于 2021-10-27 16:37:02

4个 Python 优化提速的小技巧!

本帖最后由 棉花糖不甜 于 2021-10-27 16:38 编辑

  一、 避免全局变量

import math

size = 10000
for x in range(size):
    for y in range(size):
      z = math.sqrt(x) + math.sqrt(y)

  许多程序员刚开始会用 Python 语言写一些简单的脚本,当编写脚本时,通常习惯了直接将其写为全局变量,例如上面的代码。但是,由于全局变量和局部变量实现方式不同,定义在全局范围内的代码运行速度会比定义在函数中的慢不少。通过将脚本语句放入到函数中,通常可带来 15% - 30% 的速度提升。

import math

def main():# 定义到函数中,以减少全部变量使用
    size = 10000
    for x in range(size):
      for y in range(size):
            z = math.sqrt(x) + math.sqrt(y)

main()

  二、避免

  2.1 避免模块和函数属性访问

# 不推荐写法。代码耗时:14.5秒
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()

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

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()

  在第 1 节中我们讲到,局部变量的查找会比全局变量更快,因此对于频繁访问的变量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()

  除了math.sqrt外,computeSqrt函数中还有.的存在,那就是调用list的append方法。通过将该方法赋值给一个局部变量,可以彻底消除computeSqrt函数中for循环内部的.使用。

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()

  2.2 避免类内属性访问

import math
from typing import List

class DemoClass:
    def __init__(self, value: int):
      self._value = value
   
    def computeSqrt(self, size: int) -> List:
      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)
      result = demo_instance.computeSqrt(size)

main()

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

import math
from typing import List

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

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

main()

  三、 避免不必要的抽象

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 = x

def main():
    size = 1000000
    for i in range(size):
      demo_instance = DemoClass(size)
      value = demo_instance.value
      demo_instance.value = i

main()

  任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装代码时,都会让代码变慢。大部分情况下,需要重新进行审视使用属性访问器的定义是否有必要,使用getter/setter函数对属性进行访问通常是 C/C++ 程序员遗留下来的代码风格。如果真的没有必要,就使用简单属性。

class DemoClass:
    def __init__(self, value: int):
      self.value = value# 避免不必要的属性访问器

def main():
    size = 1000000
    for i in range(size):
      demo_instance = DemoClass(size)
      value = demo_instance.value
      demo_instance.value = i

main()

  四、 避免数据复制

  4.1 避免无意义的数据复制

def main():
    size = 10000
    for _ in range(size):
      value = range(size)
      value_list =
      square_list =

main()

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

def main():
    size = 10000
    for _ in range(size):
      value = range(size)
      square_list = # 避免无意义的复制

main()

  另外一种情况是对 Python 的数据共享机制过于偏执,并没有很好地理解或信任 Python 的内存模型,滥用 copy.deepcopy()之类的函数。通常在这些代码中是可以去掉复制操作的。

  4.2 交换值时不使用中间变量

def main():
    size = 1000000
    for _ in range(size):
      a = 3
      b = 5
      temp = a
      a = b
      b = temp

main()

  上面的代码在交换值时创建了一个临时变量temp,如果不借助中间变量,代码更为简洁、且运行速度更快。

def main():
    size = 1000000
    for _ in range(size):
      a = 3
      b = 5
      a, b = b, a# 不借助中间变量

main()


  4.3 字符串拼接用join而不是+

import string
from typing import List

def concatString(string_list: List) -> str:
    result = ''
    for str_i in string_list:
      result += str_i
    return result

def main():
    string_list = list(string.ascii_letters * 100)
    for _ in range(10000):
      result = concatString(string_list)

main()

  当使用a + b拼接字符串时,由于 Python 中字符串是不可变对象,其会申请一块内存空间,将a和b分别复制到该新申请的内存空间中。因此,如果要拼接 n 个字符串,会产生 n-1 个中间结果,每产生一个中间结果都需要申请和复制一次内存,严重影响运行效率。而使用join()拼接字符串时,会首先计算出需要申请的总的内存空间,然后一次性地申请所需内存,并将每个字符串元素复制到该内存中去。

import string
from typing import List

def concatString(string_list: List) -> str:
    return ''.join(string_list)# 使用 join 而不是 +

def main():
    string_list = list(string.ascii_letters * 100)
    for _ in range(10000):
      result = concatString(string_list)

main()
  【免责声明】本文部分系转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与联系我们,我们会予以更改或删除相关文章,以保证您的权益!


页: [1]
查看完整版本: 4个 Python 优化提速的小技巧!