在阅读python日志记录模块的源代码时,我发现了这样的代码:
# next bit filched from 1.5.2's inspect.py
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except:
return sys.exc_info()[2].tb_frame.f_back
为什么?等于这个吗?
def currentframe():
return sys.exc_info()[2].tb_frame.f_back
答案 0 :(得分:2)
根据sys.exc_info
的官方文档,您需要在任何堆栈框架中都有一个异常才能获取(type, value, traceback)
的元组。如果没有异常处理,您将得到一个具有None
值的元组。堆栈框架可以是:当前堆栈,或者是函数的调用堆栈,或者是调用方(函数)本身。在日志记录中,我们仅关注当前堆栈的traceback
(注意sys.exc_info()[2]
),因此将不得不引发Exception才能访问元组值。这是摘录自文档:
此函数返回三个值的元组,它们给出有关当前正在处理的异常的信息。返回的信息特定于当前线程和当前堆栈帧。如果当前堆栈帧未处理异常,则从调用堆栈帧或其调用者等获取信息,依此类推,直到找到正在处理异常的堆栈帧为止。在这里,“处理异常”被定义为“执行except子句”。对于任何堆栈帧,只能访问有关当前正在处理的异常的信息。
如果堆栈上任何地方都没有异常处理,则为元组 包含三个无值的返回。否则,值 返回的是(类型,值,回溯)。它们的含义是:类型获取 处理的异常的类型(BaseException的子类); 值获取异常实例(异常类型的实例); traceback获取一个traceback对象(请参阅参考手册),该对象 在异常处封装调用堆栈 最初发生。
sys._getframe([depth])从调用堆栈返回帧对象。如果给出了可选的整数 depth ,则返回在堆栈顶部下方多次调用的帧对象。深度的默认值为零,将帧返回到调用堆栈的顶部。
要考虑的另一个重要点是,不能保证此函数在所有Python实现中都存在。我们知道CPython拥有它。来自logging/__init__.py
的以下代码执行此检查。请注意,currentframe()
是lambda函数。
if hasattr(sys, '_getframe'):
currentframe = lambda: sys._getframe(3)
这意味着:如果python实现中存在sys._getframe(),则从调用堆栈的顶部返回第三个框架对象。如果sys
不具有此功能作为属性,则下面的else
语句将引发一个异常,以从Traceback
捕获帧对象。
else: #pragma: no cover
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back
为了更好地理解这个概念,我使用了上面的if-else
代码来构建示例(没有双关语)。这得益于出色的解释here。以下示例包含3个函数,这些函数保存在名为main.py
的文件中。
#main.py
def get_current_frame(x):
print("Reached get_current_frame")
if hasattr(sys, '_getframe'):
currentframe = lambda x: sys._getframe(x)
else: #pragma: no cover
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back
return currentframe
def show_frame(num, frame):
print("Reached show_frame")
print(frame)
print(" frame = sys._getframe(%s)" % num)
print(" function = %s()" % frame(num).f_code.co_name)
print(" file/line = %s:%s" % (frame(num).f_code.co_filename, frame(num).f_lineno))
def test():
print("Reached test")
for num in range(4):
frame = get_current_frame(num)
show_frame(num, frame)
#function call
test()
使用python main.py
运行此代码时,我们得到以下输出:
Reached test
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0AE8>
frame = sys._getframe(0)
function = <lambda>()
file/line = main.py:74
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0B70>
frame = sys._getframe(1)
function = show_frame()
file/line = main.py:96
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0AE8>
frame = sys._getframe(2)
function = test()
file/line = main.py:89
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0B70>
frame = sys._getframe(3)
function = <module>()
file/line = main.py:115
说明:
函数get_current_frame(x):该函数包含来自if-else
的{{1}}语句的相同代码。唯一的区别是,我们将 depth 参数logging/__init__.py
传递给该函数,x
函数使用该参数以该 depth
lambda
。
函数show_frame(num,frame):此函数currentframe = lambda: sys._getframe(x)
是 frame对象,frame函数以其 depth进行调用,print
,调用方函数名称,例如。 sys._getframe(num)
.. etc。 ,即执行调用功能代码的文件的文件名以及当前行号。在调用函数的代码中。 show_frame()
是f_code
返回的框架对象的属性,并且是代码对象。 sys._getframe()
是此代码对象的属性,并返回定义该代码对象的名称(您可以打印co_name
进行检查)。同样,f_code
检索文件名,co_filename
检索当前行号。您可以在inspect文档中找到有关这些属性的说明,该说明还用于有趣地获取框架对象。您还可以编写一些隔离的代码来了解这些属性的工作方式。例如。下面的代码获取当前帧f_lineno
(即:堆栈顶部的帧对象,深度0(默认值)),并为该帧打印代码对象的文件名(我正在运行此代码frameobj
)。
main_module.py
调用栈不是太深,因为只有一个函数调用
import sys
frameobj = sys._getframe()
print(frameobj.f_code.co_filename)
#output:
main_module.py
。如果我们更改代码以使帧位于深度1,则会得到一个
错误:
_getframe()
功能测试():此功能获取深度 Traceback (most recent call last):
File "main_module.py", line 3, in <module>
frameobj = sys._getframe(1)
ValueError: call stack is not deep enough
在某个范围内的当前帧对象,然后为该num
调用show_frame()
和框架对象。
调用num
时,调用堆栈为: test-> get_current_frame-> show_frame 。在后续调用中,堆栈为 get_current_frame ---> show_frame ,直到test()
的循环为for
中的range(4)完成为止。如果我们从顶部检查输出,则堆栈顶部的框架的深度为0:
test()
,而调用函数是lambda函数本身。行号frame = sys._getframe(0)
中的74是当前行号。调用此函数的时间(将其想象为该帧的最后一个光标位置)。最后,我们看一下堆栈底部的框架。这也是日志记录中使用的框架对象(深度为3):
file/line = main.py:74
在日志记录中,我们需要3的深度才能到达调用程序函数的堆栈框架。
我们还可以使用我们先前的玩具示例来理解这个概念。由于堆栈不太深,因此我们将当前帧设为 depth 0。
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0B70>
frame = sys._getframe(3)
function = <module>()
file/line = main.py:115
现在,如果我的Python实现没有import sys
frameobj = sys._getframe()
print(frameobj.f_code.co_name)
#Output:
<module>
的{{1}}属性怎么办?在这种情况下,_getframe()
中的代码将执行并引发异常,以从sys
获取当前帧。以下函数执行此操作,此处的调用函数再次为else
(注意输出):
traceback
<module>
返回当前Exception返回的回溯帧def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
# test = 'x' + 1
raise Exception
except Exception:
_type, _value, _traceback = sys.exc_info()
print("Type: {}, Value:{}, Traceback:{}".format(_type, _value, _traceback))
print("Calling function:{}, Calling file: {}".format(sys.exc_info()[2].tb_frame.f_back.f_code.co_name, sys.exc_info()[2].tb_frame.f_back.f_code.co_filename))
return sys.exc_info()[2].tb_frame.f_back
currentframe()
#Output:
Type: <class 'Exception'>, Value:, Traceback:<traceback object at 0x0000000002EFEB48>
Calling function:<module>, Calling file: main.py
的帧对象。我们可以通过打印return语句f_back
进行检查,然后得到类似tb_frame
这说明了日志记录模块如何捕获当前帧。
那么,以后在日志记录源代码中使用print(sys.exc_info()[2].tb_frame.f_back)
是什么?您会在这里找到它:
<frame object at 0x000000000049B2C8>
上面的函数获取调用者函数的当前框架,并在以后使用此信息来获取与我们先前访问的属性相同的属性(文件名等)。
答案 1 :(得分:1)
很明显-在出现某些异常之前,没有exc_info
(异常信息)。因此,我们需要引发异常来访问其信息并从中获取调用堆栈。
这似乎是访问当前调用堆栈的最简单方法。