我想知道——当我有机会——当我不再需要Python中的单连接链表时,我是否应该分解它,或者我是否不应该打扰自己。
列表的例子:
class Link:
def __init__(self, next=None):
self.next = next
L = Link(Link(Link(Link())))
断开链接:
def break_(link):
if link is not None:
break_(link.next)
link.next = None # link broken
# application
break_(L)
树的例子:
class Node:
def __init__(self, l=None, r=None):
self.l = l
self.r = r
T =
Node(
Node(
Node(),
Node()
),
Node(
Node(),
Node()
),
)
断开链接:
def break_(node):
if node is not None:
break_(node.l)
break_(node.r)
node.l = None # link broken
node.r = None # link broken
# application
break_(T)
基本上,我想知道的是,在这种情况下编写代码的性能最佳方法是什么。GC准备好处理大型链接结构了吗?难道它不需要运行可能很长的带有计数器之类的dfse来确定哪些对象可以被释放吗?断开链接并给GC一堆在任何地方都没有引用的松散对象不是更简单吗?我可以对此进行基准测试,但我正在寻找一个解释(最好是来自Karl),如果有的话。谢谢你。
您可以在类中添加__del__
方法,以查看对象何时即将被清理:
class Node:
def __init__(self, name, l=None, r=None):
self.name = name
self.l = l
self.r = r
def __del__(self):
print(f'__del__ {self.name}')
T =
Node('0',
Node('0L',
Node('0LL'),
Node('0LR'),
),
Node('0R',
Node('0RL'),
Node('0RR'),
),
)
del T # decreases the reference count of T by 1
对于del T
,T
所引用的对象的引用计数减少了1。在这种情况下,引用计数恰好达到0。CPython知道这一点,因为它将引用计数与每个对象一起存储。当一个对象的引用计数达到0时,该对象将被标记为清理。这种清理在控制权交还给用户代码之前进行。因此,对于上面的示例,这意味着T
最初引用的对象立即被清理。这首先调用__del__
,然后调用相应的c代码进行清理。这反过来遍历对象的实例字典,并将存储在那里的其他对象的引用计数减少1。如果另一个对象的引用计数在该进程中达到0,它也被标记为清理。然后重复此过程,直到没有对象被标记为需要清理,并且将控制权交还给主循环。
这是示例的输出:
__del__ 0
__del__ 0L
__del__ 0LL
__del__ 0LR
__del__ 0R
__del__ 0RL
__del__ 0RR
如上所述,当0
被清理时,它引用的所有其他对象的引用计数减少1(即0L
和0R
),如果引用计数达到0,也会被清理。
如果您通过将相应的实例属性设置为None
来手动中断列表的链接,这会增加额外的开销,因为它必须实际修改内存中的所有这些对象(更新它们的实例字典)。子对象达到0的引用计数在这里更像是一个副产品。还要注意,由于clean
函数优先执行深度,因此清理顺序发生了变化:
def clean(node):
if node is not None:
clean(node.l)
clean(node.r)
node.l = None # this updates the object in memory
node.r = None
clean(T)
生产
__del__ 0LL
__del__ 0LR
__del__ 0RL
__del__ 0RR
__del__ 0L
__del__ 0R
__del__ 0