Python为开发人员提供了许多好处,其中最大的好处之一是其几乎无忧的内存管理。开发人员无需为Python中的对象和数据结构手动分配、跟踪和释放内存。运行时会为您完成这一切,因此您可以专注于解决真正的问题,而不是争论机器级别的细节。尽管如此,即使对于没有经验的Python用户,了解Python的垃圾收集和内存管理的工作原理也是有益的。了解这些机制将帮助您避免在更复杂的项目中可能出现的性能问题。您还可以使用Python的内置工具来监视程序的内存管理行为。Python如何管理内存每个Python对象都有一个引用计数,也称为引用计数。refcount是持有对给定对象的引用的其他对象的总数。当您添加或删除对对象的引用时,数字会增加或减少。当一个对象的引用计数变为零时,该对象将被释放并释放其内存。什么是参考?允许通过名称或通过另一个对象中的访问器访问有关对象的任何内容。下面是一个简单的例子:x="Hellothere"当我们向Python发出这个命令时,幕后发生了两件事:字符串"Hellothere"被创建并作为Python对象存储在内存中。名称x在本地命名空间中创建并指向对象,这会将其引用计数增加1到1。如果我们说y=x,那么引用计数将再次增加到2。每当xandy超出范围或从其命名空间中删除时,字符串的引用计数将针对每个名称递减1。一旦x和y超出范围或被删除,字符串的引用计数变为0并被删除。现在,假设我们创建一个包含如下字符串的列表:x=["Hellothere",2,False]字符串保留在内存中,直到列表本身被删除或包含该字符串的元素从列表中删除删除。这些操作中的任何一个都会导致唯一持有字符串引用的东西消失。现在考虑这个例子:x="Hellothere"y=[x]如果我们从中删除第一个元素y,或者完全删除列表y,该字符串仍在内存中。这是因为名称x包含对它的引用。Python中的引用循环引用计数在大多数情况下工作正常。但有时您会遇到这样的情况,即两个对象各自持有对彼此的引用。这称为参考周期。在这种情况下,对象的引用计数永远不会达到零,也永远不会从内存中删除。这是一个人为的例子:x=SomeClass()y=SomeOtherClass()x.item=yy.item=x因为x和y保持对彼此的引用,所以它们永远不会从系统中删除-即使没有其他Anything引用任何他们。Python自己的运行时为对象生成引用循环实际上是很常见的。一个示例是带有包含对异常本身的引用的回溯对象的异常。在Python的早期版本中,这是一个问题。具有引用周期的对象会随着时间的推移而累积,这对于长时间运行的应用程序来说可能是个大问题。但是Python此后引入了循环检测和用于管理引用循环的垃圾收集系统。Python垃圾收集器(gc)Python的垃圾收集器检测具有引用循环的对象。它通过跟踪作为“容器”的对象(例如列表、字典、自定义类实例)并确定其中哪些在其他地方不可访问来实现。一旦这些对象被挑选出来,垃圾收集器就会通过确保它们的引用计数可以安全地降为零来删除它们。绝大多数Python对象没有引用周期,因此垃圾收集器不需要24/7全天候运行。相反,垃圾收集器使用一些试探法来降低运行频率并尽可能高效地运行。当Python解释器启动时,它会跟踪已分配但未释放的对象数。绝大多数Python对象都是短暂的,因此它们出现和消失的速度很快。但随着时间的推移,会出现更多长寿的对象。一旦超过一定数量的此类对象累积,垃圾收集器就会运行。每次垃圾收集器运行时,它都会收集所有在收集中幸存下来的对象,并将它们放在一个称为一代的组中。这些“第一代”对象在引用周期中扫描的频率较低。任何在垃圾收集器中幸存下来的第一代对象最终都会迁移到第二代,在那里它们被扫描的频率较低。此外,垃圾收集器不会跟踪所有内容。例如,始终跟踪用户创建的类等复杂对象。但是只包含简单对象(如整数和字符串)的字典将不会被跟踪,因为该特定字典中的任何对象都不会包含对其他对象的引用。无法保存对其他元素(如整数和字符串)的引用的简单对象永远不会被跟踪。如何使用gc模块一般情况下,垃圾收集器不需要调整就可以正常运行。Python的开发团队选择了反映最常见的现实场景的默认值。但是如果你真的需要调整垃圾收集的工作方式,你可以使用Python的gc模块。gc模块为垃圾收集器的行为提供了一个编程接口,并提供了对被跟踪对象的可见性。gc当您确定不需要垃圾收集器时,您可以做的一件有用的事情是将其关闭。例如,如果您有一个短时间运行的脚本,它会堆积很多对象,那么您就不需要垃圾收集器。当脚本结束时,所有内容将被清除。为此,您可以使用命令gc.disable()禁用垃圾收集器。稍后,您可以使用gc.enable()重新启用它。您还可以使用gc.collect()手动运行收集周期。一个常见的应用程序是管理程序中生成许多临时对象的性能密集型部分。您可以在程序的那部分禁用垃圾收集,然后在最后手动运行收集并重新启用收集。另一个有用的垃圾收集优化是gc.freeze()。发出此命令时,垃圾收集器当前跟踪的所有内容都被“冻结”,或者被列为免于未来收集扫描。这样,以后的扫描就可以跳过这些对象。如果您有一个程序在开始之前导入库并设置大量内部状态,您可以在所有工作完成后发出gc.freeze()。这避免了垃圾收集器寻找无论如何都不太可能被删除的东西。(如果要对冻结的对象再次执行垃圾回收,请使用gc.unfreeze()。)使用gc调试垃圾回收您还可以使用gc调试垃圾回收行为。如果你有太多的对象堆积在内存中并且没有被垃圾收集,你可以使用gc的检查工具来找出哪些对象可能持有对这些对象的引用。如果您想知道哪些对象持有给定对象的引用,您可以使用gc.get_referrers(obj)列出它们。您还可以使用gc.get_referents(obj)查找给定对象引用的任何对象。如果您不确定给定对象是否是垃圾收集的候选者,gc.is_tracked(obj)会告诉您该对象是否被垃圾收集器跟踪。如前所述,请记住垃圾收集器不会跟踪“原子”对象(例如整数)或仅包含原子对象的元素。如果您想自己查看正在收集哪些对象,可以使用gc.set_debug(gc.DEBUG_LEAK|gc.DEBUG_STATS)设置垃圾收集器的调试标志。这会将有关垃圾收集的信息写入stderr。它将所有被垃圾收集的对象保存在一个只读列表中。避免Python内存管理中的陷阱如前所述,如果您仍然在某处引用对象,对象可能会堆积在内存中并且无法被收集。这并不是Python垃圾收集本身的失败。垃圾收集器无法判断您是否不小心保留了对某物的引用。让我们以一些防止对象被收集的指针结束。注意对象范围如果你将对象1作为对象2的属性(例如类),对象2将需要在对象1之前超出范围:obj1=MyClass()obj2.prop=obj1更重要的是,如果发生这种情况作为某些其他操作的副作用,例如将对象2作为参数传递给对象1的构造函数,您可能没有意识到对象1持有一个引用:obj1=MyClass(obj2)另一个例子:如果您将对象推入模块级列表并忘记列表,该对象一直存在,直到它从列表中删除,或者直到列表本身不再有任何对它的引用。但是如果列表是一个模块级对象,它可能会一直存在直到程序终止。简而言之,注意您的对象可能被另一个对象持有的方式,这并不总是很明显。使用weakref避免引用循环Python的weakref模块允许您创建对其他对象的弱引用。弱引用不会增加对象的引用计数,因此只有弱引用的对象才是垃圾回收的对象。weakrefs的一个常见用途是对象缓存。你不想仅仅因为它有一个缓存条目就保留一个被引用的对象,所以你使用weakref作为缓存条目。手动中断引用循环最后,如果您知道给定对象包含对另一个对象的引用,您总是可以手动中断对该对象的引用。例如,如果您有instance_of_class.ref=other_object,则可以在准备删除instance_of_class时设置instance_of_class.ref=None。通过了解Python的内存管理工作原理,我们了解其垃圾收集系统如何帮助优化Python程序中的内存,以及如何使用标准库和其他地方提供的模块来控制内存使用和垃圾收集。原标题:Python垃圾回收和gc模块
