首页 > 编程语言 >python 虚拟机框架-名字、作用域和名字空间

python 虚拟机框架-名字、作用域和名字空间

时间:2022-11-04 14:57:37浏览次数:43  
标签:作用域 虚拟机 Python 名字 print frame def

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对象,而这些对象会被链接起来,形成一条执行环境链表。

image

在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()

对于上面的代码,执行结果为:
image

从执行结果可以看到,在函数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}')

执行结果为:
image

名字、作用域和名字空间

在上面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”的异常。

更为有趣的是编译之后的字节码中,我们将上边的代码编译为字节码,结果如下:
image

比较一下就会发现,同样的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

相关文章

  • 虚拟机创建流程 – libvirt篇
    1.虚拟机创建流程–libvirt篇 1.libvirt的架构1.1.基本架构图1.2.接口调用方式2.从nova到libvirt3.libvirt的接口调用流程4.qemuProcessStart()启......
  • ubuntu20.04 从安装 kvm、qemu、libvirt 到进入虚拟机
    安装环境可行性检测验证CPU是否支持硬件虚拟化grep-Eoc'(vmx|svm)'/proc/cpuinfo//数字大于0,则代表CPU支持硬件虚拟化,反之则不支持检查VT是否在BIOS中启用a......
  • jenkins部署-虚拟机篇
    前言本篇介绍jenkins常见的几种安装部署方式,同时满足虚拟机和容器环境的部署。软件部署架构采取master<->slave的方式进行,在实际生产环境一般采用本方式进行部署,该方式能......
  • 用类的作用域隐藏信息
    如下所示:如果在foo里、定义类型别名、枚举、内部类、并且它们都处于public范围内、那么外部是可以用 类名:: 这种方式去拿到foo类里面定义的东西的 但是、如果......
  • 动静态、继承本质、对象名字查找顺序
    目录动静态方法面向对象之继承的概念继承的本质名字的查找顺序经典类与新式类派生方法动静态方法在类中定义的函数有多种特性1.绑定给对象的方法classStudent:sc......
  • 虚拟机和Ubuntu的安装
    虚拟机和Ubuntu的安装时间:2022-11-03一、虚拟机下载链接由于windows11只能安装VMwareWorkstation16的版本,所以我只下载了16的版本,以下是百度网盘链接。链接:https://p......
  • 面向对象2、动静态方法、继承、名字查找顺序、经典类、新式类、派生方法
    目录今日内容概要今日内容详细动静态方法面向对象之继承的概念继承的本质名字的查找顺序经典类与新式类派生方法作业今日内容概要动静态方法面向对象之继承理论继承基......
  • 【解决一个小问题】proto文件中的enum,去掉长长的重复的enum名字
    在proto中定义的enum,通常类型名字都会带上enum的前缀,很丑陋,如何去掉呢?enumDataSourceType{NotUse=0;MySQL=1;ElasticSearch=2;}生成后的代码如下:cons......
  • 如何在VM虚拟机里安装Linux系统
    很多企业在做数据抓取的时候往往需要多台服务器同时运行,在降低成本的同时还能方便操控服务器就显得尤为重要。这篇文章介绍了在VMware虚拟机里安装Linux操作系统的方法,文中......
  • pd18.1.0虚拟机如何一键安装Windows 11 懒人版
    pd18.1.0虚拟机如何一键安装Windows11懒人版?入手了Mac电脑后,由于需要用到Windows软件,又嫌安装双系统太复杂,这时候Mac就用到了安装虚拟机,目前最好用的虚拟机是ParallelsD......