python如何在'for'循环中处理对象实例化

时间:2012-10-11 02:25:34

标签: python memory-management for-loop instantiation reference-counting

我有一个非常复杂的课程:

class C:
    pass

我有这个测试代码:

for j in range(10):
    c = C()
    print c

给出了:

<__main__.C instance at 0x7f7336a6cb00>
<__main__.C instance at 0x7f7336a6cab8>
<__main__.C instance at 0x7f7336a6cb00>
<__main__.C instance at 0x7f7336a6cab8>
<__main__.C instance at 0x7f7336a6cb00>
<__main__.C instance at 0x7f7336a6cab8>
<__main__.C instance at 0x7f7336a6cb00>
<__main__.C instance at 0x7f7336a6cab8>
<__main__.C instance at 0x7f7336a6cb00>
<__main__.C instance at 0x7f7336a6cab8>

可以很容易地看到Python切换到两个不同的值。 在某些情况下,这可能是灾难性的(例如,如果我们将对象存储在其他复杂对象中)。

现在,如果我将对象存储在List中:

lst = []
for j in range(10):
    c = C()
    lst.append(c)
    print c

我明白了:

<__main__.C instance at 0x7fd8f8f7eb00>
<__main__.C instance at 0x7fd8f8f7eab8>
<__main__.C instance at 0x7fd8f8f7eb48>
<__main__.C instance at 0x7fd8f8f7eb90>
<__main__.C instance at 0x7fd8f8f7ebd8>
<__main__.C instance at 0x7fd8f8f7ec20>
<__main__.C instance at 0x7fd8f8f7ec68>
<__main__.C instance at 0x7fd8f8f7ecb0>
<__main__.C instance at 0x7fd8f8f7ecf8>
<__main__.C instance at 0x7fd8f8f7ed40>

解决了这个问题。

所以现在,我不得不问一个问题...... 有没有人可以解释复杂的单词(我的意思是,深入)Python如何使用对象引用? 我想,这是一个优化问题(为了节省内存,或防止泄漏,......)

非常感谢。

编辑: 好的,让我们更具体一点。我很清楚python有时候会收集垃圾......但是,在我的情况下:

我有一个由Cython定义的类返回的列表:类'Network'管理'Node'列表(Network和Node类都在Cython extension中定义)。每个节点都有一个对象[然后被转换为(void *)]'userdata'对象。节点列表从cython内部填充,而UserData填充在Python脚本中。所以在python中,我有以下内容:

...
def some_python_class_method(self):
    nodes = self.netBinding.GetNetwork().get_nodes()
    ...
    for item in it:
        a_site = PyLabSiteEvent()
        #l_site.append(a_site)        # WARN : Required to get an instance on 'a_site' 
                                      #        that persits - workaround...
    item.SetUserData(a_site)

稍后使用相同的cython getter在同一个python类中重用此节点列表:

def some_other_python_class_method(self, node):
    s_data = node.GetUserData()
    ...

所以,似乎在节点列表的UserDatas中进行了存储,我的python脚本完全失明并且正在释放/重用内存。它通过第二次引用(但显然是python方面的第一次),使用额外的列表(这里:'l_site')。 这就是为什么我必须更多地了解Python本身,但似乎我在Python和Cython之间实现通信的方式是导致必须面对的问题的原因。

3 个答案:

答案 0 :(得分:12)

这里没有必要“复杂”: 在第一个示例中,您不保留对名称“c”引用的对象的其他引用 - 在循环的后续迭代中运行“c = C()”行中的代码时,先前在“c”中保存的一个引用“失去了。

由于标准Python使用引用计数来跟踪它应该从内存中删除对象的时间,因为此时前一个循环交互的对象的引用计数达到0,它被销毁,并且其内存可用于其他对象。

为什么你有两个不断变化的值?因为此时创建了新迭代中的对象 - 即当Python在=中执行c = C()右侧的表达式时,预先存在的迭代的对象仍然存在,由名称引用c - 所以新对象是在另一个内存位置构造的。 Python然后继续将新对象赋值给c,此时如上所述销毁前一个对象 - 这意味着在下一个(第三个)迭代中,该内存将可用于{的新实例。 {1}}。

在第二个例子中,新创建的对象永远不会松散引用,因此根本不释放它们的内存 - 新对象总是占用新的内存位置。

最重要的是: 使用高级语言(如Python或其他语言)的目的是不必担心内存分配。语言会照顾你。在这种情况下,正如您所注意到的那样,CPython(标准)实现会做正确的事情。其他实现(如Pypy或Jython)在上述示例中的每个实例的“内存位置”方面可能有完全不同的行为,但所有符合实现的(包括这3个)将从“视点”的“视角”完全相同。 Python程序:(1)它可以访问它保持引用的实例,(2)这些实例的数据无论如何都没有被破坏或损坏。

答案 1 :(得分:5)

这似乎并不复杂。

在第一个示例中,第二次循环时,0x7f7336a6cb00处的内存被第一次迭代期间创建的C实例占用。因此,Python为新的C对象分配下一个内存块0x7f7336a6cab8。

但是,只要创建第二个C对象并将其分配给c,就不会在0x7f7336a6cb00处向现在的孤立对象留下任何引用。因此,通过循环Python的第三时间可以重新使用此位置的内存用于新对象。当然,当它这样做时,0x7f7336a6cab8上的对象不再有对它的引用,并且该内存位置可用于循环第四时间循环。

但是,在第二个示例中,通过将对象附加到列表中,您为您创建的每个对象保留一个引用。由于这些对象始终至少有一个对它们的引用,因此它们“存在”的内存永远不可用于释放和回收。因此,Python每次都会分配新的内存。

第一个例子中产生的危险幻觉就是 - 幻觉。只要对您创建的对象存在引用,就会维护该对象。当不存在任何引用时,Python释放对象使用的内存是安全的,因为程序不可能再使用该对象。

答案 2 :(得分:3)

在每个循环中,你更改了c引用的对象,因此原始文件是不可访问的,Python可以自由地删除它(因为如果你再也无法访问它,为什么要保留任何对象?) 。那时,它在同一个位置有空闲内存,似乎可以重用它。我不确定这有什么令人惊讶的。如果解释器没有重用内存,那么你的运行速度会非常快。

当您将对象添加到列表中时,这种情况不会发生,因为该对象仍然可以访问,因此Python无法将其删除(因为您可能会再次使用它)。

这不应该导致问题,因为Python在你仍然可以使用时不会删除对象,所以如果你“将对象存储在某个复杂的对象中”,它们将保持可访问状态并赢得他们的记忆。重复使用(至少在物体消失之前)。