这篇文章主要介绍“Python函数的实现原理源码分析”,在日常操作中,相信很多人在Python函数的实现原理源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python函数的实现原理源码分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧! 函数是任何一门编程语言都具备的基本元素,它可以将多个要执行的操作组合起来,一个函数代表了一系列的操作。而且在调用函数时会干什么来着,没错,要创建栈帧,用于函数的执行。Python 一切皆对象,函数也不例外。函数在底层是通过 PyFunctionObject 结构体实现的,定义在 funcobject.h 中。我们来实际获取一下这些成员,看看它们在 Python 中是如何表现的。func_code:函数的字节码func_globals:global 名字空间func_defaults:函数参数的默认值func_kwdefaults:只能通过关键字的方式传递的 “参数” 和 “该参数的默认值” 组成的字典在前面加上一个 *,就表示后面的参数必须通过关键字的方式传递。因为如果不通过关键字参数传递的话,那么无论多少个位置参数都会被 * 接收,无论如何也不可能传递给 name、age。我们知道如果定义了 *args,那么函数可以接收任意个位置参数,然后这些参数以元组的形式保存在 args 里面。但这里我们不需要,我们只是希望后面的参数必须通过关键字参数传递,因此前面写一个 * 即可,当然写 *args 也是可以的。func_closure:闭包对象注意:查看闭包属性我们使用的是内层函数,不是外层的 foo。func_doc:函数的 docstringfunc_name:函数的名字当然不光是函数,方法、类、模块都有自己的名字。func_dict:函数的属性字典因为函数在底层也是由一个类实例化得到的,所以它可以有自己的属性字典,只不过这个字典一般为空。当然啦,我们也可以整点骚操作:所以虽然叫函数,但它也是由某个类型对象实现的。func_weakreflist:弱引用列表Python无法获取这个属性,底层没有提供相应的接口,关于弱引用此处就不深入讨论了。func_module:函数所在的模块类、方法、协程也有 __module__ 属性。func_annotations:类型注解func_qualname:全限定名全限定名要更加地完整一些。但是这个类底层没有暴露给我们,我们不能直接用,因为函数通过 def 创建即可,不需要通过类型对象来创建。前面我们说到函数在底层是由 PyFunctionObject 结构体实现的,它里面有一个 func_code 成员,指向一个 PyCodeObject 对象,函数就是根据它创建的。因为 PyCodeObject 是对一段代码的静态表示,Python 编译器在将源代码编译之后,对里面的每一个代码块(code block)都会生成一个、并且是唯一一个 PyCodeObject 对象。该对象包含了这个代码块的一些静态信息,也就是可以从源代码当中看到的信息。比如某个函数对应的代码块里面有一个 a = 1 这样的表达式,那么符号 a 和整数 1、以及它们之间的联系就是静态信息,而这些信息会被静态存储起来。符号 a 被存在符号表 co_varnames 中;整数 1 被存在常量池 co_consts 中;这两者之间是一个赋值语句,因此会有两条指令:LOAD_CONST 和 STORE_FAST,它们存在字节码指令序列 co_code 中;以上这些信息是编译的时候就可以得到的,因此 PyCodeObject 对象是编译之后的结果。但是 PyFunctionObject 对象是何时产生的呢?显然它是 Python 代码在运行时动态产生的,更准确的说,是虚拟机在执行一个 def 语句的时候创建的。当虚拟机在当前栈帧中执行字节码时发现了 def 语句,那么就代表发现了新的 PyCodeObject 对象,因为它们是可以层层嵌套的。所以虚拟机会根据这个 PyCodeObject 对象创建对应的 PyFunctionObject 对象,然后将函数名和 PyFunctionObject 对象(函数体)组成键值对放在当前的 local 空间中。而在 PyFunctionObject 对象中,也需要拿到相关的静态信息,因此会有一个 func_code 成员指向 PyCodeObject。除此之外,PyFunctionObject 对象中还包含了一些函数在执行时所必需的动态信息,即上下文信息。比如 func_globals,就是函数在执行时关联的 global 空间,说白了就是在局部变量找不到的时候能够找全局变量,可如果连 global 空间都没有的话,那即便想找也无从下手呀。而 global 作用域中的符号和值必须在运行时才能确定,所以这部分必须在运行时动态创建,无法静态存储在 PyCodeObject 中,因此要根据 PyCodeObject 对象创建 PyFunctionObject 对象。总之一切的目的,都是为了更好地执行字节码。我们举个例子:调用的时候,会从 local 空间中取出符号 foo 对应的 PyFunctionObject 对象。然后根据这个 PyFunctionObject 对象创建 PyFrameObject 对象,也就是为函数创建一个栈帧,随后将执行权交给新创建的栈帧,并在新创建的栈帧中执行字节码。通过上面的分析,我们知道了函数是虚拟机在遇到 def 语句的时候创建的,并保存在 local 空间中。当我们通过函数名()的方式调用时,会从 local 空间取出和函数名绑定的函数对象,然后执行。那么问题来了,函数(对象)是怎么创建的呢?或者说虚拟机是如何完成 PyCodeObject 对象到 PyFunctionObject 对象之间的转变呢?显然想了解这其中的奥秘,就必须从字节码入手。源代码很简单,定义一个变量 name 和函数 foo,然后调用函数。显然源代码在编译之后会产生两个 PyCodeObject,一个是模块的,一个是函数 foo 的,我们来看一下。上面有一个有趣的现象,就是源代码的行号。之前看到源代码的行号都是从上往下、依次增大的,这很好理解,毕竟一条一条解释嘛。但是这里却发生了变化,先执行了第 6 行,之后再执行第 4 行。如果是从 Python 层面的函数调用来理解的话,很容易一句话就解释了,因为函数只有在调用的时候才会执行,而调用肯定发生在创建之后。但是从字节码的角度来理解的话,我们发现函数的声明和实现是分离的,是在不同的 PyCodeObject 对象中。确实如此,虽然函数名和函数体是一个整体,但是虚拟机在实现的时候,却在物理上将它们分离开了。正所谓函数即变量,我们可以把函数当成普通的变量来处理。函数名就是变量名,它位于模块对应的 PyCodeObject 的符号表中;函数体就是变量指向的值,它是基于一个独立的 PyCodeObject 构建的。换句话说,在编译时,函数体里面的代码会位于一个新的 PyCodeObject 对象当中,所以函数的声明和实现是分离的。至此,函数的结构就已经非常清晰了。所以函数名和函数体是分离的,它们存储在不同的 PyCodeObject 对象当中。分析完结构之后,重点就要落在 MAKE_FUNCTION 指令上了,我们说当遇到 def foo(a, b) 的时候,就知道要创建函数了。在语法上这是函数的声明语句,但从虚拟机的角度来看这其实是函数对象的创建语句。所以下面我们就要分析一下这个指令,看看它到底是怎么将一个 PyCodeObject 对象变成一个 PyFunctionObject 对象的。整个步骤很好理解,先通过 LOAD_CONST 将 PyCodeObject 对象和符号 foo 压入栈中。然后执行 MAKE_FUNCTION 的时候,将两者从栈中弹出,再加上当前栈帧对象中维护的 global 名字空间,三者作为参数传入 PyFunction_NewWithQualName 函数中,从而构建出相应的函数对象。上面的函数比较简单,如果再加上类型注解、以及默认值,会有什么效果呢?这里我们加上了类型注解和默认值,看看它的字节码指令会有什么变化?0 LOAD_CONST 0 (‘satori’)
2 STORE_NAME 0 (name)
4 LOAD_CONST 7 ((1, 2))
6 LOAD_NAME 1 (int)
8 LOAD_NAME 1 (int)
10 LOAD_CONST 3 ((‘a’, ‘b’))
12 BUILD_CONST_KEY_MAP 2
14 LOAD_CONST 4 ()
不难发现,在构建函数时会先将默认值以元组的形式压入运行时栈;然后再根据使用了类型注解的参数和类型构建一个字典,并将这个字典压入运行时栈。后续创建函数的时候,会将默认值保存在 func_defaults 成员中,类型注解对应的字典会保存在 func_annotations 成员中。基于类型注解和描述符,我们便可以像静态语言一样,实现函数参数的类型约束。介绍完描述符之后,我们会举例说明。我们通过一些骚操作,来更好地理解一下函数。之前说
16 LOAD_CONST 5 ('foo')
18 MAKE_FUNCTION 5 (defaults, annotations)
......
......
这篇文章主要介绍“无法加载控制器 1.php错误如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“无法加载控制器 1.php错误如何解决”文章能帮助大家解决问题。 文件不存在当我们在代码中指定了错误的控制器文件名时…
免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。