在 python pdb 下,当在 __del__ 方法中到达断点时,如果 gc 是原因,堆栈会是什么样子?

Under python pdb when a breakpoint is reached in a __del__ method, what would the stack look like if gc is the cause?

情况:我在 __del__ 方法中放置了一个断点。当使用 del 显式删除对象时,堆栈很清楚 del 是调用 __del__ 的原因。

但是,如果由于 out-of-scope/garbage 收集而调用了 __del__,堆栈会是什么样子?是否清楚 gc 在堆栈上,或者它看起来像执行 gc 时代码所在的任何点?

或者这在其他方面是荒谬的?

经过一些基础研究,至少对于 CPython(由 Anaconda 生成)的答案如下,基于下面的脚本及其附带的输出。基本上,当范围丢失时收集对象时,堆栈将指向实例化帧的行,在 return 处收集对象。 IE。如果在从 afunc 调用 return 之后收集了一个对象,则 __del__ 调用帧将位于调用 afunc 的位置。

虽然我无法重现示例,但推测如果发生延迟收集,__del__ 的调用帧将在延迟收集发生的点。

示例脚本:

 import traceback


class ClassWithDel:

    def __init__(self, whoami='Default'):
        self._whoami = whoami

    def __del__(self):
        print(f'{self._whoami}: In __del__')
        traceback.print_stack()

    def whoami(self):
        print(f'I am {self._whoami}')


def afunc():
    cwd = ClassWithDel()
    cwd.whoami()


def closure():
    cwd = ClassWithDel()

    def closure_with_cwd():
        cwd.whoami()

    return closure_with_cwd


def afunc_with_closure():
    closure_cwd = closure()
    closure_cwd()


def afunc_overwrite():
    cwd = ClassWithDel()
    cwd.whoami()
    cwd = ClassWithDel('NotDefault')
    cwd.whoami()


def return_list():
    results = [ClassWithDel('l1'), ClassWithDel('l2')]
    return results


def afunc_overwrite_list():
    l = return_list()
    l2 = l[1]
    l = l2


if __name__ == '__main__':
    print('Starting with afunc...')
    afunc()
    print('\nStarting with closure...')
    afunc_with_closure()
    print('\nStarting with overwrite...')
    afunc_overwrite()
    print('\nStarting list overwrite...')
    afunc_overwrite_list()

结果:

$ python -m gc_test
Starting with afunc...
I am Default
Default: In __del__
  File ".../lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File ".../lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "gc_test.py", line 56, in <module>
    afunc()
  File "gc_test.py", line 11, in __del__
    traceback.print_stack()

Starting with closure...
I am Default
Default: In __del__
  File ".../lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File ".../lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "gc_test.py", line 58, in <module>
    afunc_with_closure()
  File "gc_test.py", line 11, in __del__
    traceback.print_stack()

Starting with overwrite...
I am Default
Default: In __del__
  File ".../lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File ".../lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "gc_test.py", line 60, in <module>
    afunc_overwrite()
  File "gc_test.py", line 39, in afunc_overwrite
    cwd = ClassWithDel('NotDefault')
  File "gc_test.py", line 11, in __del__
    traceback.print_stack()
I am NotDefault
NotDefault: In __del__
  File ".../lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File ".../lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "gc_test.py", line 60, in <module>
    afunc_overwrite()
  File "gc_test.py", line 11, in __del__
    traceback.print_stack()

Starting list overwrite...
l1: In __del__
  File ".../lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File ".../lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "gc_test.py", line 62, in <module>
    afunc_overwrite_list()
  File "gc_test.py", line 51, in afunc_overwrite_list
    l = l2
  File "gc_test.py", line 11, in __del__
    traceback.print_stack()
l2: In __del__
  File ".../lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File ".../lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "gc_test.py", line 62, in <module>
    afunc_overwrite_list()
  File "gc_test.py", line 11, in __del__
    traceback.print_stack()