堆栈和框架有啥区别?

Posted

技术标签:

【中文标题】堆栈和框架有啥区别?【英文标题】:What is the difference between a stack and a frame?堆栈和框架有什么区别? 【发布时间】:2014-07-13 22:40:02 【问题描述】:

在什么情况下我想使用一种而不是另一种?

有什么区别:

>>> import inspect
>>> print(inspect.getouterframes(inspect.currentframe()))
[(<frame object at 0x8fc262c>, '<stdin>', 1, '<module>', None, None)]

还有:

>>> import traceback
>>> traceback.extract_stack()
[('<stdin>', 1, '<module>', None)]

更新:

另一个:

>>> import sys
>>> print(sys._getframe().f_trace,sys._getframe().f_code)
(None, <code object <module> at 0x8682a88, file "<stdin>", line 1>)

我不明白这里的细微差别:

堆栈帧 框架对象 堆栈跟踪

更新 2,距离提出问题有一点时间,但非常相关

Explain the concept of a stack frame in a nutshell

【问题讨论】:

@BrenBarn 这显然不是唯一的区别,因为getouterframes 包含更多数据。 请注意,虽然差异似乎微不足道,但traceback.extract_stack() 不包含对堆栈帧的引用这一事实非常重要。您对框架对象的每个引用都是内存泄漏(因为该框架引用的任何内容现在都不符合 gc 条件),因此在长时间运行的程序中这样做是一个很大的禁忌。 @roippi 不,不一定是内存泄漏。如果您不在局部变量中保留对框架对象的引用,那么您甚至没有引用循环。如果你确实有一个参考循环,你可以明确地打破它。即使您不理会引用循环,如果可以从该循环访问具有__del__ 方法的任何内容并且您没有运行Python 3.4 或更高版本(请参阅PEP 442),这也只是内存泄漏.这些复杂的条件使得正确使用它棘手,但并非不可能,即使在一次运行数周的服务器中也是如此。 @delnan 我并不是在暗示 gc 无法检测到循环引用。我的意思是,如果您坚持那些框架对象引用(例如,***全局错误处理程序或其他东西),就会发生不好的事情。 @roippi 好吧,这只是一种可怕的说法,即“很多东西都可以从框架中获取”。这与任何其他参考文献没有根本不同。 【参考方案1】:

好吧,因为这似乎更多的是关于堆栈帧/调用堆栈的一般情况,让我们来看看:

def f():
    try:
        g()
    except:
        # WE WILL DO THINGS HERE

def g():
    h()

def h():
    raise Exception('stuff')

#CALL
f()

当我们在h() 时,call stack 上有 4 帧。

[top level]
 [f()]
  [g()]
   [h()] #<-- we're here

(如果我们尝试在堆栈中放入超过sys.getrecursionlimit() 的帧,我们将得到RuntimeError,这是python 版本的*** ;-))

“外部”指的是调用堆栈中我们上方的所有内容(字面意思是“向上”方向)。所以按照顺序,g,然后是f,然后是顶层(模块)级别。同样,“内部”指的是调用堆栈中向下的所有内容。如果我们在f() 中捕获异常,则该回溯对象将引用所有内部堆栈帧,这些帧已展开以使我们到达该点。

def f():
    try:
        g()
    except:
        import inspect
        import sys
        #the third(last) item in sys.exc_info() is the current traceback object
        return inspect.getinnerframes(sys.exc_info()[-1])

这给出了:

[(<frame object at 0xaad758>, 'test.py', 3, 'f', ['        g()\n'], 0), 
(<frame object at 0x7f5edeb23648>, 'test.py', 10, 'g', ['    h()\n'], 0), 
(<frame object at 0x7f5edeabdc50>, 'test.py', 13, 'h', ["    raise Exception('stuff')\n"], 0)]

正如所料,三个内框 f、g 和 h。现在,我们可以获取最后一个框架对象(来自h() 的那个)并请求它的外部 框架:

[(<frame object at 0x7f6e996e6a48>, 'test.py', 13, 'h', ["    raise Exception('stuff')\n"], 0), 
(<frame object at 0x1bf58b8>, 'test.py', 10, 'g', ['    h()\n'], 0), 
(<frame object at 0x7f6e99620240>, 'test.py', 7, 'f', ['        return inspect.getinnerframes(sys.exc_info()[-1])\n'], 0), 
(<frame object at 0x7f6e99725438>, 'test.py', 23, '<module>', ['print(inspect.getouterframes(f()[-1][0]))\n'], 0)]

所以,你去吧,这就是正在发生的一切:我们只是在调用堆栈中导航。为了比较,下面是traceback.extract_stack(f()[-1][0]) 给出的:

[('test.py', 23, '<module>', 'print(traceback.extract_stack(f()[-1][0]))'), 
('test.py', 7, 'f', 'return inspect.getinnerframes(sys.exc_info()[-1])'), 
('test.py', 10, 'g', 'h()'), 
('test.py', 13, 'h', "raise Exception('stuff')")]

请注意这里与getouterframes 相比的倒序,以及减少的输出。事实上,如果你眯着眼睛,这基本上看起来就像一个常规的回溯(嘿,它,只是格式多了一点)。

总结:inspect.getouterframestraceback.extract_stack 都包含重现您在日常回溯中通常看到的所有信息; extract_stack 只是删除了对堆栈帧的引用,因为一旦您从给定的帧向外格式化堆栈跟踪,就不再需要它们是很常见的。

【讨论】:

【参考方案2】:

The documentation inspect 模块说:

当以下函数返回“帧记录”时,每条记录是六项的元组:帧对象、文件名、当前行的行号、函数名、源上下文的行列表代码,以及该列表中当前行的索引。

The documentation traceback 模块说:

“预处理”堆栈跟踪条目是一个 4 元组(文件名、行号、函数名、文本)

因此区别在于框架记录还包括框架对象和一些上下文行,而回溯只包括调用堆栈中各个行的文本(即导致extract_stack调用的调用)。

如果您只想打印回溯,您可以使用来自traceback 的堆栈。正如文档所暗示的,这是为向用户显示而处理的信息。如果您想对调用堆栈实际执行任何操作(例如,从调用帧中读取变量),则需要从 inspect 访问框架对象。

【讨论】:

以上是关于堆栈和框架有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

调用堆栈和堆栈有啥区别?

分段错误和堆栈溢出有啥区别?

ios堆栈溢出中的uuid,udid和设备令牌有啥区别

使用实体框架 Cascade 和 ClientCascade 有啥区别?

API、框架和中间件有啥区别? [关闭]

PowerMock、EasyMock 和 Mockito 框架有啥区别? [复制]