python函数调用栈帧 python函数调用堆栈过程

python中的frame是什么意思?Frame对象表示执行?。硎境绦蛟诵惺焙饔谜恢械哪骋恢?。
想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取 。可以使用sys模块的_getframe()函数、或inspect模块的currentframe()函数获取当前栈帧 。
f_back: 调用栈的前一帧 。
f_code: 栈帧对应的code对象 。
f_locals: 用在当前栈帧时与内建函数locals()相同,但你可以先获取其他帧然后使用这个属性获取那个帧的locals() 。
f_globals: 用在当前栈帧时与内建函数globals()相同,但你可以先获取其他帧……
示例: 假设在下面代码的第四行打断点
函数被断点停止住时刻的frame信息如下
更多Python知识请关注Python视频教程栏目 。
关于Python中的一段为Python脚本添加行号脚本C语言有__LINE__来表示源代码的当前行号 , 经常在记录日志时使用 。Python如何获取源代码的当前行号?
The C Language has the __LINE__ macro, which is wildly used in logging, presenting the current line of the source file. And how to get the current line of a Python source file?
exception输出的函数调用栈就是个典型的应用:
A typical example is the output of function call stack when an exception:
python代码
File "D:\workspace\Python\src\lang\lineno.py", line 19, in module
afunc()
File "D:\workspace\Python\src\lang\lineno.py", line 15, in afunc
errmsg = 1/0
ZeroDivisionError: integer division or modulo by zero
那么我们就从错误栈的输出入手 , traceback模块中:
Now that, Let's begin with the output of an exception call stack, in the traceback module:
python代码
def print_stack(f=None, limit=None, file=None):
"""Print a stack trace from its invocation point.
The optional 'f' argument can be used to specify an alternate
stack frame at which to start. The optional 'limit' and 'file'
arguments have the same meaning as for print_exception().
"""
if f is None:
try:
raise ZeroDivisionError
except ZeroDivisionError:
f = sys.exc_info()[2].tb_frame.f_back
print_list(extract_stack(f, limit), file)
def print_list(extracted_list, file=None):
"""Print the list of tuples as returned by extract_tb() or
extract_stack() as a formatted stack trace to the given file."""
if file is None:
file = sys.stderr
for filename, lineno, name, line in extracted_list:
_print(file,
'File "%s", line %d, in %s' % (filename,lineno,name))
if line:
_print(file, '%s' % line.strip())
traceback模块构造一个ZeroDivisionError,并通过sys模块的exc_info()来获取运行时上下文 。我们看到,所有的秘密都在tb_frame中,这是函数调用栈中的一个帧 。
traceback constructs an ZeroDivisionError, and then call the exc_info() of the sys module to get runtime context. There, all the secrets hide in the tb_frame, this is a frame of the function call stack.
对,就是这么简单!只要我们能找到调用栈frame对象即可获取到行号!因此,我们可以用同样的方法来达到目的,我们自定义一个lineno函数:
Yes, It's so easy! If only a frame object we get, we can get the line number! So we can have a similar implemetation to get what we want, defining a function named lineno:
python代码
import sys
def lineno():
frame = None
try:
raise ZeroDivisionError
except ZeroDivisionError:
frame = sys.exc_info()[2].tb_frame.f_back
return frame.f_lineno
def afunc():
# if error
print "I have a problem! And here is at Line: %s"%lineno()
是否有更方便的方法获取到frame对象?当然有!
Is there any other way, perhaps more convinient, to get a frame object? Of course YES!
python代码
def afunc():
# if error
print "I have a proble! And here is at Line: %s"%sys._getframe().f_lineno
类似地,通过frame对象,我们还可以获取到当前文件、当前函数等信息,就像C语音的__FILE__与__FUNCTION__一样 。其实现方式,留给你们自己去发现 。
Thanks to the frame object, similarly, we can also get current file and current function name, just like the __FILE__ and __FUNCTION__ macros in C. Debug the frame object, you will get the solutions.
python 为什么要进行递归限制因为递归的效率较低 , 如果不进行限制可能运行一个py文件会花费大量的时间
ue5python原理Python先把代码(.py文件)编译成字节码python函数调用栈帧 , 交给字节码虚拟机,然后解释器一条一条执行字节码指令,从而完成程序的执行 。
1.1python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后解释器会从编译得到的PyCodeObject对象中一条一条执行字节码指令,
并在当前的上下文环境中执行这条字节码指令,从而完成程序的执行 。Python解释器实际上是在模拟操作中执行文件的过程 。PyCodeObject对象
中包含python函数调用栈帧了字节码指令以及程序的所有静态信息,但没有包含程序运行时的动态信息——执行环境(PyFrameObject)
2. 字节码
字节码在python解释器程序里对应的是PyCodeObject对象
.pyc文件是字节码在磁盘上的表现形式
2.1从整体上看python函数调用栈?。篛S中执行程序离不开两个概念python函数调用栈?。航毯拖叱?。python中模拟python函数调用栈帧了这两个概念 , 模拟进程和线程的分别是PyInterpreterState和
PyTreadState 。即:每个PyThreadState都对应着一个帧栈,python解释器在多个线程上切换 。当python解释器开始执行时,它会先进行一
些初始化操作,最后进入PyEval_EvalFramEx函数,它的作用是不断读取编译好的字节码,并一条一条执行,类似CPU执行指令的过程 。函数内部
主要是一个switch结构,根据字节码的不同执行不同的代码 。
3. .pyc文件
PyCodeObject对象的创建时机是模块加载的时候,及import
Python test.py会对test.py进行编译成字节码并解释执行,但是不会生成test.pyc
如果test.py加载了其他模块,如import urlib2, Python会对urlib2.py进行编译成字节码,生成urlib2.pyc,然后对字节码进行解释
如果想生成test.pyc,我们可以使用Python内置模块py_compile来编译 。
加载模块时,如果同时存在.py和pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc 。
4. PyCodeObject
Python代码的编译结果就是PyCodeObject对象
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置参数个数 */
int co_nlocals; /* 局部变量个数 */
int co_stacksize; /* 栈大小 */
int co_flags;
PyObject *co_code; /* 字节码指令序列 */
PyObject *co_consts; /* 所有常量集合 */
PyObject *co_names; /* 所有符号名称集合 */
PyObject *co_varnames; /* 局部变量名称集合 */
PyObject *co_freevars; /* 闭包用的的变量名集合 */
PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */
/* The rest doesn’t count for hash/cmp */
PyObject *co_filename; /* 代码所在文件名 */
PyObject *co_name; /* 模块名|函数名|类名 */
int co_firstlineno; /* 代码块在文件中的起始行号 */
PyObject *co_lnotab; /* 字节码指令和行号的对应关系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;
5. .pyc文件格式
加载模块时 , 模块对应的PyCodeObject对象被写入.pyc文件
6.分析字节码
6.1解析PyCodeObject
Python提供了内置函数compile可以编译python代码和查看PyCodeObject对象
6.2指令序列co_code的格式
opcode oparg opcode opcode oparg …
1 byte 2 bytes 1 byte 1 byte 2 bytes
Python内置的dis模块可以解析co_code
7. 执行字节码
Python解释器的原理就是模拟可执行程序再X86机器上的运行 , X86的运行时栈帧如下图
Python解释器的原理就是模拟上述行为 。当发生函数调用时,创建新的栈帧,对应Python的实现就是PyFrameObject对象 。
PyFrameObject对象创建程序运行时的动态信息,即执行环境
7.1 PyFrameObject
typedef struct _frame{
PyObject_VAR_HEAD //"运行时栈"的大小是不确定的
struct _frame *f_back; //执行环境链上的前一个frame,很多个PyFrameObject连接起来形成执行环境链表
PyCodeObject *f_code; //PyCodeObject 对象,这个frame就是这个PyCodeObject对象的上下文环境
PyObject *f_builtins; //builtin名字空间
PyObject *f_globals; //global名字空间
PyObject *f_locals; //local名字空间
PyObject **f_valuestack; //"运行时栈"的栈底位置
PyObject **f_stacktop; //"运行时栈"的栈顶位置
//...
int f_lasti; //上一条字节码指令在f_code中的偏移位置
int f_lineno; //当前字节码对应的源代码行
//...
//动态内存 , 维护(局部变量 cell对象集合 free对象集合 运行时栈)所需要的空间
PyObject *f_localsplus[1];
} PyFrameObject;
每一个 PyFrameObject对象都维护了一个 PyCodeObject对象,这表明每一个 PyFrameObject中的动态内存空间对象都和源代码中的一段Code相对应 。
【python函数调用栈帧 python函数调用堆栈过程】python函数调用栈帧的介绍就聊到这里吧,感谢你花时间阅读本站内容 , 更多关于python函数调用堆栈过程、python函数调用栈帧的信息别忘了在本站进行查找喔 。

    推荐阅读