Python内存管理指南
- 2019 年 11 月 27 日
- 筆記
原标题 | Memory Management in Python
作 者 | Jun Wu
翻 译 | 天字一号
审 校 | 唐里、Pita
对于软件开发人员而言,了解内存管理很重要。随着Python在软件开发中得到广泛使用,编写高效的Python代码通常意味着需要编写内存高效使用的代码。随着大数据的使用越来越广泛,内存管理的重要性不容忽视。无效的内存管理会导致应用程序和服务器端组件运行缓慢。内存泄漏通常会导致花费大量时间进行测试和调试,它还会严重破坏数据处理并引起并发处理问题。
即使大多数Python的内存管理都是由Python内存管理器完成的,但了解最佳编码实践以及Python的内存管理器的工作方式仍可以使代码更高效和可维护。
对于软件开发人员而言,内存管理最重要的部分是内存分配。了解在计算机的物理或虚拟内存中分配空白空间的过程至关重要。有两种类型的内存分配。
静态内存分配 – 程序在编译时分配了内存。例如在C / C ++中,您只能声明具有固定大小的静态数组。在编译时分配内存。堆栈用于实现静态分配。在这种情况下,不能重用内存。
static int a=10;
动态内存分配 – 在运行时为程序分配了内存。例如,在C / C ++中,您可以使用一元运算符new声明数组。内存在运行时分配。堆用于实现动态分配。在这种情况下,不需要时可以释放和重用内存。
int *p; p=new int;
关于Python的好处是Python中的所有东西都是对象。这意味着动态内存分配是Python内存管理的基础。当不再需要对象时,Python内存管理器将自动从它们中回收内存。
Python是使用C编程语言实现的高级编程语言。Python内存管理器管理Python的内存分配。有一个私有heap,其中包含所有Python对象和数据结构。Python内存管理器按需管理Python堆。Python内存管理器具有特定于对象的分配器,可为int,string等特定对象分别分配内存。在此之下,原始内存分配器与操作系统的内存管理器进行交互,以确保私有堆上有空间。
Python内存管理器管理称为“块”的内存块。相同大小的块的集合构成了“池”。池是在Arenas上创建的,在堆= 64池上分配了256kB的内存块。如果对象被销毁,则内存管理器将用相同大小的新对象填充此空间。
方法和变量在堆栈存储器中创建。每当创建方法和变量时,都会创建一个堆栈框架。只要返回方法,这些框架就会自动销毁。
在堆内存中创建对象和实例变量。一旦返回变量和函数,将对垃圾对象进行垃圾回收。
请务必注意,Python内存管理器不一定会将内存释放回操作系统,而是将内存返回给python解释器。Python有一个小的对象分配器,用于分配内存以供进一步使用。在长时间运行的进程中,您可能有未使用内存的增量保留。
使用联接将项目添加到列表是高效Python代码的最佳做法
无需将line1,line2分别添加到mymsg,而是使用list和join。
不要这样:
mymsg=’line1n’ mymsg+=’line2n’
最好这样:
mymsg=[‘line1’,’line2'] ‘n’.join(mymsg)
避免对字符串使用+运算
如果可以避免,请不要使用+运算符进行串联。由于字符串是不可变的,因此每次将元素添加到字符串时,Python都会创建一个新的字符串和一个新的地址。这意味着每次更改字符串时都需要分配新的内存。
不要这样做:
msg=’hello’+mymsg+’world’
更好的选择:
msg=’hello %s world’ % mymsg
使用Generators
生成器允许您创建一个函数,一次返回一个项目,而不是一次返回所有项目。这意味着,如果您有大型数据集,则不必等待整个数据集都可以访问。
def __iter__(self): return self._generator() def _generator(self): for itm in self.items(): yield itm
将评估置于循环之外
如果要遍历数据,则可以使用正则表达式的缓存版本。
match_regex=re.compile(“foo|bar”) for i in big_it: m = match_regex.search(i) ….
将函数分配给局部变量
Python访问局部变量要比全局变量有效得多。将函数分配给局部变量,然后使用它们。
myLocalFunc=myObj.func for i in range(n): myLocalFunc(i)
使用内置函数和库
尽可能使用内置函数和库。内置函数通常使用最佳内存使用方法来实现。
不要这样做:
mylist=map(str.lower, oldlist)
与循环相比,使用关键字参数创建数据集的更好选择:
mycounter = Counter (a = 1, b = 2, c = 3, d = 5, e = 6, f = 7, g = 8) for i in mycounter.elements()
通过使用itertools摆脱不必要的循环
itertools(https://docs.python.org/3/library/itertools.html)为您节省了大量的循环时间。它还摆脱了代码的复杂性。
不要这样做:
mylist=[] for shape in [True, False]: for weight in (1, 5): firstlist=firstlist+function(shape, weight)
最好这样:
from itertools import product, chain list(chain.from_iterable(function(shape, weight) for weight, shape in product([True, False], range(1, 5))))
覆盖_new_并利用元类来进行安全和内存管理——作者@maxwell flitton
重写_new_并利用元类进行安全和内存管理,方法是@maxwell flitton 在执行Singleton和Flyweight模式时,重写__new__并利用元类对内存管理也非常有用和安全。例如,这是一个读取Yaml文件的dict对象的示例。因为它的元类一经定义便是单例设计模式,因此可以将其导入系统中的任何位置并再次定义,并且解释器将仅指向初始对象。它减少了内存占用并确保了安全性。不管团队中的其他开发人员多么初级,它们都不会导致重复的对象,从而防止它们更改系统某一部分中的命令,并防止另一部分中引用另一条命令。
class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class ConfigDict(dict, metaclass=Singleton): def __init__(self): super().__init__(self.read_config_file()) @staticmethod def read_config_file(): “”” Reads config file based on path passed when running app. :return: (dict) loaded data from yml file “”” config_file_path = sys.argv[-1] if not config_file_path.endswith(“.yml”): raise ConfigDictError(message=”yml file not passed into flask app but {} instead”.format(config_file_path)) return yaml.load(open(str(config_file_path)), Loader=yaml.FullLoader)
如何检查Python代码的性能
您可以使用配置文件模块(例如cProfile和Profile:https://docs.python.org/3/library/profile.html)进行性能检查。
python -m cProfile [-o output_file][-s sort_order](-m module | myscript.py)
阅读有关Python内存管理的更多信息,可查看以下资源:
- 流利的Python:清晰,简洁,有效的编程(http://geni.us/xA2PP)
- Python Cookbook:精通Python 3(http://geni.us/yAoO1S)
- 真正的Python:Python中的内存管理(https://realpython.com/python-memory-management/)
- Python.org内存管理(https://docs.python.org/3/c-api/memory.html)
- Atem Golubin:Python中的内存管理(https://rushter.com/blog/python-memory-managment/)