page contents

7个Python内存优化技巧,你用过几个?

本文讲述了7个python内存优化技巧,你用过几个?具有很好的参考价值,希望对大家有所帮助。一起跟随好学星城小编过来看看吧,具体如下:

attachments-2024-03-iffaBLVp65eeb77e03b53.png本文讲述了7个python内存优化技巧,你用过几个?具有很好的参考价值,希望对大家有所帮助。一起跟随好学星城小编过来看看吧,具体如下:

当我们的项目变得越来越大时,高效管理计算资源是一个不可避免的要求。不幸的是,与低级语言如C或C++相比,Python在内存效率方面似乎不够。那么,现在应该更改编程语言吗?

当然不是。事实上,有许多方法可以显著优化Python程序的内存使用,从优秀的模块和工具到先进的数据结构和算法。本文将聚焦于Python的内置机制,并介绍7个原始但有效的内存优化技巧。掌握这些技巧将显著提高我们的Python编程技能。

1. 在类定义中使用__slots__

Python作为一种动态类型语言,在面向对象编程方面更加灵活。一个很好的例子是在运行时向Python类中添加额外的属性和方法的能力。例如,下面的代码定义了一个名为Author的类。最初它有两个属性name和age。但是我们可以很容易地在后来添加一个额外的属性:

class Author:

    def __init__(self, name, age):

        self.name = name

        self.age = age

me = Author('Yang Zhou', 30)

me.job = 'Software Engineer'

print(me.job)

然而,每个硬币都有两面。这种灵活性在底层浪费了更多的内存。因为Python类的每个实例都维护一个特殊的字典(__dict__)来存储实例变量。这个字典由于其基于哈希表的实现方式而固有地内存效率低下,占用大量内存。

在大多数情况下,我们不需要在运行时更改实例的变量或方法,而且在类定义之后__dict__将不会改变。因此,如果我们能避免维护__dict__字典,那就更好了。Python为此提供了一个神奇的属性:__slots__。它通过指定类的所有有效属性的名称来充当白名单:

class Author:

    __slots__ = ('name', 'age')

    def __init__(self, name, age):

        self.name = name

        self.age = age

me = Author('Yang Zhou', 30)

me.job = 'Software Engineer'

print(me.job)

#AttributeError: 'Author' object has no attribute 'job'

如上所示,我们不能再在运行时添加job属性。因为__slots__白名单只定义了两个有效属性name和age。从理论上讲,由于属性现在是固定的,Python不需要为其维护一个字典。它只需为__slots__中定义的属性分配必要的内存空间。

让我们编写一个简单的比较程序,看看它是否确实起作用:

import sys

class Author:

    def __init__(self, name, age):

        self.name = name

        self.age = age

class AuthorWithSlots:

    __slots__ = ['name', 'age']

    def __init__(self, name, age):

        self.name = name

        self.age = age

# Creating instances

me = Author('Yang', 30)

me_with_slots = AuthorWithSlots('Yang', 30)

# Comparing memory usage

memory_without_slots = sys.getsizeof(me) + sys.getsizeof(me.__dict__)

memory_with_slots = sys.getsizeof(me_with_slots)  # __slots__ classes don't have __dict__

print(memory_without_slots, memory_with_slots)

# 152 48

print(me.__dict__)

# {'name': 'Yang', 'age': 30}

print(me_with_slots.__dict__)

# AttributeError: 'AuthorWithSlots' object has no attribute '__dict__'

正如上面的代码所演示的,由于使用了__slots__,me_with_slots实例不具有__dict__字典。与必须保留额外字典的me实例相比,这有效地节省了内存资源。

2. 使用生成器

生成器是Python中的惰性求值版本的列表。它们就像元素生成工厂:仅在调用next()方法时生成一个项目,而不是一次计算所有项目。因此,当处理大型数据集时,它们非常内存高效。

def number_generator():

    for i in range(100):

        yield i

numbers = number_generator()

print(numbers)

print(next(numbers))

#0

print(next(numbers))

#1

上面的代码展示了编写和使用生成器的基本示例。关键字yield是生成器定义的核心。应用它意味着只有在调用next()方法时才会产生项目i。现在,让我们比较一下生成器和列表,看看哪个更内存高效:

import sys

numbers = []

for i in range(100):

    numbers.append(i)

def number_generator():

    for i in range(100):

        yield i

numbers_generator = number_generator()

print(sys.getsizeof(numbers_generator))

#112

print(sys.getsizeof(numbers))

#920

上述程序的结果证明了使用生成器可以显著节省内存使用。顺便说一下,如果我们将列表推导式的方括号改成括号,它将变成生成器表达式。这是在Python中定义生成器的更简便的方法:

import sys

numbers = [i for i in range(100)]

numbers_generator = (i for i in range(100))

print(sys.getsizeof(numbers_generator))

#112

print(sys.getsizeof(numbers))

#920

3. 利用内存映射文件支持大文件处理

内存映射文件I/O,简称“mmap”,是一种操作系统级别的优化。

它实现了需求分页,因为文件内容并不立即从磁盘读取,并且最初根本不使用物理RAM。实际从磁盘读取是在特定位置被访问时以懒惰的方式执行的。—— 维基百科

简单来说,当使用mmap技术内存映射文件时,它在当前进程的虚拟内存空间中直接创建文件的映射,而不是将整个文件加载到内存中。映射而不是加载整个文件可以节省大量内存。

听起来很复杂?幸运的是,Python已经提供了一个用于使用这种技术的内置模块,因此我们可以轻松利用它,而不必考虑操作系统级别的实现。例如,这是在Python中使用mmap进行文件处理的方法:

import mmap

with open('test.txt', "r+b") as f:

    # memory-map the file, size 0 means whole file

    with mmap.mmap(f.fileno(), 0) as mm:

        # read content via standard file methods

        print(mm.read())

        # read content via slice notation

        snippet = mm[0:10]

        print(snippet.decode('utf-8'))

如上所演示的,Python使得内存映射文件I/O技术的使用变得方便。我们所需要做的就是简单地应用`mmap.mmap()`方法,然后使用标准文件方法或甚至切片表示法处理打开的对象。

4. 减少全局变量的使用

全局变量在程序运行期间始终驻留在内存中,因为它们具有全局范围。因此,如果一个全局变量保存一个大型数据结构,它将在整个程序生命周期中占用内存,可能导致内存使用效率低下。我们应该在Python代码中尽量减少全局变量的使用。

5. 利用逻辑运算符的短路求值

这个技巧似乎微妙,但巧妙地使用它将极大地节省程序的内存使用。例如,下面是一个简单的代码片段,根据两个函数返回的布尔值得到最终结果:

result_a = expensive_function_a()

result_b = expensive_function_b()

result = result_a if result_a else result_b

上面的代码能够工作,但实际上执行了两个内存效率低下的函数。获取相同结果的更聪明的方法如下:

result = expensive_function1() or expensive_function2()

由于逻辑运算符遵循短路求值规则,上述代码中的`expensive_function2()`将不会在`expensive_function1()`为True时执行。这将节省不必要的内存使用。

6. 谨慎选择数据类型

一位经验丰富的Python开发者会仔细而准确地选择数据类型。因为在某些场景中,使用一个数据类型比另一个更节省内存。

元组比列表更节省内存

由于元组是不可变的(在创建后不能更改),它允许Python在内存分配方面进行优化。然而,列表是可变的,因此需要额外的空间来容纳潜在的修改。

import sys

my_tuple = (1, 2, 3, 4, 5)

my_list = [1, 2, 3, 4, 5]

print(sys.getsizeof(my_tuple))

#80

print(sys.getsizeof(my_list))

#120

如上面的片段所示,即使它们包含相同的元素,元组`my_tuple`使用的内存比列表更少。因此,如果在创建后不需要更改数据,我们应该更喜欢使用元组而不是列表。

数组比列表更节省内存

Python中的数组要求元素是相同的数据类型(例如,全部整数或全部浮点数),但列表可以存储不同类型的对象,这必然需要更多的内存。因此,如果列表的元素都是相同类型,使用数组会更节省内存:

import sys

import array

my_list = [i for i in range(1000)]

my_array = array.array('i', [i for i in range(1000)])

print(sys.getsizeof(my_list))

#8856

print(sys.getsizeof(my_array))

#4064

优秀的数据科学模块比内置数据类型更高效

Python是数据科学的主导语言。有许多强大的第三方模块和工具提供了更多的数据类型,例如NumPy和Pandas。如果我们只需要一个简单的一维数字数组,并且不需要NumPy提供的广泛功能,那么Python内置的数组可能是一个不错的选择。

但是,当涉及到复杂的矩阵操作时,对于所有数据科学家来说,使用NumPy提供的数组是第一选择,可能是最好的选择。

7. 对相同的字符串应用字符串驻留技术

下面的代码可能会使许多开发者感到困惑:

>>> a = 'Y'*4096

>>> b = 'Y'*4096

>>> a is b

True

>>> c = 'Y'*4097

>>> d = 'Y'*4097

>>> c is d

False

正如我们所知,`is`运算符用于检查两个变量是否引用内存中的同一对象。它与`==`运算符不同,后者用于比较两个对象是否具有相同的值。那么为什么`a is b`返回True,而`c is d`返回False呢?

这里有Python中的一个隐秘技巧 —— 字符串驻留技术。如果有几个值相同的小型字符串,它们将由Python隐式地进行驻留,并引用内存中的同一对象。定义小型字符串的神奇数字是4096。由于`c`和`d`的长度都是4097,它们是内存中的两个对象而不是一个。不再有隐式的字符串驻留。因此,在执行`c is d`时得到False。

字符串驻留是一种优化内存使用的强大技术。如果我们想要显式地进行驻留,`sys.intern()`方法就派上用场了:

>>> import sys

>>> c = sys.intern('Y'*4097)

>>> d = sys.intern('Y'*4097)

>>> c is d

True

顺便说一下,除了字符串驻留,Python还对小整数应用驻留技巧。我们也可以利用它进行内存优化。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

  • 发表于 2024-03-11 15:49
  • 阅读 ( 43 )
  • 分类:Python开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
小柒
小柒

1316 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1316 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章