python的虚拟机是python的核心,在.py源代码被编译器编译为字节码指令序列后,就将由python的虚拟机接手整个工作。python的虚拟机将从编译得到的PyCodeObject对象中依次读入每一条字节码指令,并在当前的上下文环境中执行这条字节码指令。如此反复运行,所有由python源代码所规定的动作都会如期望一样,一 一展开。
PyFrameObject(执行环境)
[frameobject.h]
typedef struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; /* previous frame, or NULL */
PyCodeObject *f_code; /* code segment */
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
PyObject *f_globals; /* global symbol table (PyDictObject) */
PyObject *f_locals; /* local symbol table (any mapping) */
PyObject **f_valuestack; /* points after the last local */
/* Next free slot in f_valuestack. Frame creation sets to f_valuestack.
Frame evaluation usually NULLs it, but a frame that yields sets it
to the current stack top. */
PyObject **f_stacktop;
PyObject *f_trace; /* Trace function */
char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode trace events? */
/* Borrowed reference to a generator, or NULL */
PyObject *f_gen;
int f_lasti; /* Last instruction if called */
/* Call PyFrame_GetLineNumber() instead of reading this field
directly. As of 2.3 f_lineno is only valid when tracing is
active (i.e. when f_trace is set). At other times we use
PyCode_Addr2Line to calculate the line from the current
bytecode index. */
int f_lineno; /* Current line number */
int f_iblock; /* index in f_blockstack */
char f_executing; /* whether the frame is still executing */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
} PyFrameObject;
从f_back我们可以看出一点,在python实际的执行中,会产生很多PyFrameObject对象,而这些对象会被链接起来,形成一条执行环境链表。
在Python中访问PyFrameObject对象
尽管PyFrameObject对象是一个用于Python虚拟机实现的极为隐秘的内部对象,但是Python还是提供了某种途径可以访问到PyFrameObject对象。在Python中,有一种frame object,它是对C 一级的PyFrameObject的包装。而且,非常幸运的是,Pythont提供了一个方法能方便地获取当前处于活动的frame object。这个方法就是sys 模块中的_getframe方法。
import sys
value = 3
def g():
frame = sys._getframe()
print('current fun is: %s' % frame.f_code.co_name)
caller = frame.f_back
print('caller is: %s' % caller.f_code.co_name)
print('caller is local namespace: %s' % caller.f_locals)
print('caller is global namespace: %s' % caller.f_globals)
def f():
a = 1
b = 2
global value
g()
def show():
f()
show()
对于上面的代码,执行结果为:
从执行结果可以看到,在函数f 中我们完全获得了起调用者————函数g 的一切信息,甚至包括函数g 的各个命名空间。
有读者可能对sys._getframe 是如歌实现的很感兴趣,下面我们就利用Python 的异常机制,实现一个和sys._getframe 功能相同的代码。
import sys
def get_current_frame():
try:
1 / 0
except:
# print(1111, sys.exc_info())
_type, _value, traceback = sys.exc_info()
return traceback.tb_frame.f_back
r = get_current_frame()
print(f'caller is: {r}')
print(f'caller is save all character: {r.f_code.co_names}')
执行结果为:
名字、作用域和名字空间
在上面PyFrameObject 源码中,我们看到3个独立的名字空间:builtins, globals 与 locals。名字空间对Python来说,是一个非常核心的概念,整个Python虚拟机运行的机制与“名字空间”这个概念有非常紧密的联系。在Python 中,与名字空间这个概念紧密联系的还有“名字”,“作用域”这些概念。
Python 程序的基础结构————module
每一个.py文件被视为Python的一个module。这些moudle中有一个主module, 如果你的程序是通过 python main.py 启动的,那么这个main.py就是一个主module。
一个名字(有时也称符号)就是用于代表某些事物的一个有助于记忆的字符序列。在Python中,一个标识符就是一个名字,比如变量名、函数名、类名等等。名字最终的作用不在于名字本身,而在于名字背后的那个事物。
在Python中,要使用或执行一个module, 必须首先加载一个module。加载可以用两种方式:一种是一办module的加载,通过import动作动态地加载;一种是主module的加载,通过python main.py这样的方式完成。不管一个module是如何被加载的,在加载过程中都会执行一个动作————执行module中的表达式。
[A.py]
a = 1
a += 1
def f():
print(a)
print(a)
在module A 被加载时,Python会执行“a=1”、“a+=1”、“def f()”、“print(a)”这四条语句。(没错,函数定义也会被执行)
在赋值语句被执行之后,从概念上将,我们实际上得到了一个(name, obj)这样的关联关系,称为约束。赋值语句就是建立约束的地方。在一个约束被创建之后,它不会立即消失,会长久影响程序的运行。约束的容身之处就是名字空间。在Python 中,名字空间就是一个PyDictObject对象实现的。
LGB规则
一个module对应的源文件定义了一个作用域,称为global作用域(对应globals名字空间);一个函数定义了一个local作用域(对应locals名字空间);Python自身还定义了一个最顶层的作用域————builtin作用域(对应builtins名字空间, 在这里定义了Python的builtin函数,比如dir、open、range等)
LGB规则:名字引用动作沿着locals作用域、globals作用域、buintins作用域的顺序查找名字对应的约束。
LEGB规则
a = 1
def f()
a = 2
def g():
print(a) # [1]
return g
func = f()
func() #[2]
在执行上边这个modle的时候,在执行[2]处的时候, “a=2”这个约束已经不起作用了,但是Python在执行“func=f()”时,会执行函数f中的“def g()”语句,这时Python会将约束“a=2”与函数g对应的函数对象捆绑在一起,将捆绑后的结果返回,这个捆绑起来的整体被称为“闭包”。
这里有一个相当微妙的问题,最内嵌套作用域规则是“闭包”的结果呢,还是“闭包”是最内嵌套作用域规则的实现方案?这两个问题看上去是一致的,但却隐含这谁决定谁的关系。实际上,Python实现闭包是为了实现最内嵌套作用域规则。换句话说,最内嵌套作用域规则是语言设计时的策略,即是形而上的“道”;而闭包则是实现语言时的一种方案,即是形而下的“器”。
这里显示的作用域规则通常也被称为LEGB规则,其中的E为 enclosing 的缩写,代表的正是“直接外围作用域”这个概念。
global 表达式
a = 1
def f():
print(a)
def g():
print(a) # [1]
a = 2 # [2]
print(a)
f()
g()
上边这个代码在执行时,会在[1]处报“local variable 'a' referenced before assignment”异常。没有赋值?按照LEGB规则,应该输出1 才对,并且函数f 已经老老实实输出了,在函数g 里为什么会异常?
理解这个错误的关键在于理解深刻理解最内嵌套作用域规则。这个规则的第一句话就是这个奇怪问题的症结所在,“由一个赋值语句引进的名字在这个赋值语句所在的作用域内是可见(起作用)的”。这句话的意思是,在函数g 中,虽然a=2 是在print 函数之后建立的,但是由于它们在同一个作用域,所以在[1]处是可以,a=2 这个约束的名字a 就是可见的。按照LEGB规则,在local名字空间能找到a ,所以就使用local 名字空间中的a 。但不幸的是,虽然在[1]处能找到a , 但是在print 函数之后,也就是在[2]处才能赋值动作发生,a 才能引用一个有效的对象,所以在[1]处当然应该抛出“local variable 'a' referenced before assignment”的异常。
更为有趣的是编译之后的字节码中,我们将上边的代码编译为字节码,结果如下:
比较一下就会发现,同样的print(a)指令,在函数f 和 函数g 中编译后的内容却是不一样。在函数f 中 LOAD_GLOBAL 表示是从global名字空间中获取,在函数g 中,LOAD_FAST 表示是从local名字空间中获取,也就是说Python 在编译的时候就已经知道名字藏身何处了。这正说明了Python 采用的是静态作用域规则,仅仅根据程序正文就能确定名字引用策略。同时,这个现象又一次说明最内嵌套作用域规则是指导Python 实现的“道”。
但有的时候,我们就是想要在函数输出外围作用域的名字a , 并且还要对a 进行赋值,但是这个赋值操作在我们设想中应该是改变外围作用域中的名字a 对应的对象。Python 为我们精心准备了 global 关键字。当一个作用域中出现global 时,意味着我们强制命令Python 对某个名字的引用只参考global 名字空间,而不再去管LEGB规则。
看到下面两个例子,你对global就了如指掌了:
a = 1
def f():
global a
print(a) # 输出 1
a = 2
f()
print(a) # 输出 2
a = 1
def f():
a = 2
def g():
global a
print(a) # 输出1
a += 2
return g
t = f()
t()
print(a) # 输出 3
标签:作用域,虚拟机,Python,名字,print,frame,def
From: https://www.cnblogs.com/yimeimanong/p/16854324.html