1.python中函数的工作原理

def foo():
bar() def bar():
pass

python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c函数)运行foo()函数
首先会创建一个栈帧(stack Frame),在栈帧对象的上下文里面去运行这个字节码。

import dis
print(dis.dis(foo)) #打印字节码

可以尝试着去打印foo的字节码:

关于字节码的解释:

  LOAD_GLOBAL:首先导入bar这个函数
  CALL_FUNCTION:执行bar函数
  POP_TOP:从栈的顶端去把元素打印出来
  LOAD_CONST:返回结果,这里没有return,就是None
  RETURN_VALUE:返回结果

打印bar的字节码:

print(dis.dis(bar))

这个字节码全局是唯一的,函数是全局唯一的,然后在函数里面会调用另外一个函数。
当foo调用函数bar,又会创建一个栈帧,然后将这个函数的控制权交给这个栈帧。
所有的栈帧都分配在内存中,它不是放在栈的内存上,而是放在堆的内存上,你不去释放它就会一直存在我们的内存当中。
这就决定了栈帧可以独立于调用者存在,比如就算函数不存在了,只要有指针指向bar这个栈帧,就可以对其进行控制。
(python中一切皆对象,栈帧也是对象,是一个字节码对象)

import inspect
frame = None #保存frame def foo():
bar() def bar():
global frame #引入全局变量
frame = inspect.currentframe() #将bar的frame赋给全局变量 foo()
print(frame.f_code.co_name) #bar 函数退出之后,依然可以拿到bar函数的栈帧
caller_frame = frame.f_back
print(caller_frame.f_code.co_name) #foo 也可以拿到foo函数的栈帧

2.生成器的实现原理

在静态语言中,函数调用的时候是一个栈的形式,函数调用完成之后栈就会被销毁。
下面是函数的调用过程:

PyEval_evalFrameEx会创建一个foo的栈帧对象,这个对象里面有两个属性。f_back为None,因为没有上层函数,f_code指向foo的字节码
同时PyEval_evalFrameEx也会创建一个bar的栈帧对象,f_back指向foo,f_code指向bar的字节码。
最大的特点就是栈帧对象存在于堆内存中,这样生成器才有实现的可能。

def gen_func():
yield 1
name = "ming"
yield 2
age = 28
return "kebi" #在早期的生成器版本中不能使用return

当python解释器在读取gen_fun()这个函数的时候,发现yield关键字就会将其标记为生成器函数。
gen_func()
当我们来调用这个函数的时候,就会返回一个生成器对象。
这个生成器对象是将PyFrame做了一层封装。

在PyFrameObject和PyCodeObject上面又封装了一层PyGenObject,就是python的生成器对象。
PyGenObject中gi_frame属性指向PyGrameObject,gi_code属性指向PyCodeObject。
PyFrameObject又有f_lasti和f_locals属性。
f_lasti会指向最近执行的这个代码。

可以尝试打印字节码:

def gen_func():
yield 1
name = "ming"
yield 2
age = 28
return "kebi" #在早期的生成器版本中不能使用return import dis
gen = gen_func()
print(dis.dis(gen))

查看结果:

这里面可以看到有两次yield。当我们每一次对生成器做一次调用的时候,它遇到yield就会停止。
停止了之后,就会记录f_lasti(位置)和f_locals(变量)这两个值。

可以尝试着调用打印取每一个值,f_lasti和f_locals的变化

print(gen.gi_frame.f_lasti)  #-1
print(gen.gi_frame.f_locals) #{}
next(gen)
print(gen.gi_frame.f_lasti) #
print(gen.gi_frame.f_locals) #{}
next(gen)
print(gen.gi_frame.f_lasti) #
print(gen.gi_frame.f_locals) #{'name':'ming'}

与上方字节码是一样的。

这样整个生成器对象就存在与堆内存中,可以独立存在,每次执行一次函数,就会生成一个栈帧对象。
我们可以在任何地方,只要能拿到这个栈帧对象就能够往前走。这也是python中协程的一个理论基础。

此时我们可以知道为什么生成器是一个一个返回。

3.pyc文件

当你在执行python代码的时候,会发现执行目录下面会出现.pyc文件。

[root@tuoguan resources]# ls
r1.py r1.pyc r2.py r3.py
[root@tuoguan resources]# cat r1.pyc ¶:]c@s
dZdS(tname_r1N(R(((s/tmp/demo/resources/r1.py<module>s

r1.pyc是一个二进制文件,当执行的文件中存在包的引入就会编译生成二进制文件。

当python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器则将PyCodeObject写回到pyc文件中。

当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。

所以我们应该这样来定位PyCodeObject和pyc文件,我们说pyc文件其实是PyCodeObject的一种持久化保存方式。

python中的函数、生成器的工作原理的更多相关文章

  1. Python中split()函数的用法及实际使用示例

    Python中split()函数,通常用于将字符串切片并转换为列表. 一.函数说明: split():语法:str.split(str="",num=string.count(st ...

  2. Java中的Annotation(2)----Annotation工作原理

    Java中的Annotation(2)----Annotation工作原理 分类: 编程语言2013-03-18 01:06 3280人阅读 评论(6) 收藏 举报 上一篇文章已经介绍了如何使用JDK ...

  3. Python中利用函数装饰器实现备忘功能

    Python中利用函数装饰器实现备忘功能 这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归.确保参数传递的正确,需要的朋友可以参考下   " ...

  4. python中range()函数的用法

    python中range()函数可创建一个整数列表,一般用在for循环中. range()函数语法: range(start,stop[,step]) 参数说明: star: 计数从star开始.默认 ...

  5. Python 中的函数

    学了 Python 中的数据类型,语句,接下来就来说一下 Python 中的函数,函数是结构化编程的核心.我们使用函数可以增加程序的可读性.自定义函数时使用关键字def 函数由多条语句组成.在定义函数 ...

  6. python中format函数

    python中format函数用于字符串的格式化 通过关键字 1 print('{名字}今天{动作}'.format(名字='陈某某',动作='拍视频'))#通过关键字 2 grade = {'nam ...

  7. Python中readline()函数 去除换行符

    从Python中readline()函数读取的一行内容中含有换行符\n,很多时候我们需要处理不含有换行符的字符串,此时就要去掉换行符\n. 方法是使用strip()函数. 例子如下: f = open ...

  8. Python中int()函数的用法浅析

      int()是Python的一个内部函数 Python系统帮助里面是这么说的 >>> help(int)  Help on class int in module __builti ...

  9. 【313】python 中 print 函数用法总结

    参考:python 中 print 函数用法总结 参考:Python print() 函数(菜鸟教程) 参考:Python 3 print 函数用法总结 目录: 字符串和数值类型 变量 格式化输出 p ...

  10. python中filter函数

    python中filter()函数   filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断 ...

随机推荐

  1. [转帖]crontab每小时运行一次

    crontab每小时运行一次     先给出crontab的语法格式 对于网上很多给出的每小时定时任务写法,可以说绝大多数都是错误的!比如对于下面的这种写法: 00 * * * * #每隔一小时执行一 ...

  2. ListView控件的理解——自洽理论

    写在前面的话: *标题中已经说明,是自洽理论.因此,有几率会有理解错误.但是,你不可以因此骂我. -我这个人经不起别人的批评,如果你批评我,我就,我就.... ## <第一行代码>读书笔记 ...

  3. visual studio code编辑python文件

    visual studio code 安装.通过360软件管家,查找visual studio code 下载安装即可 设置visual studio code为中文 打开进入软件,Ctrl + Sh ...

  4. CH01-ZYNQ修炼秘籍-LINUX篇-虚拟机环境搭建

    CH01基于Ubuntu系统的ZYNQ-7000开发环境的搭建 1.1概述 实验环境: Windows 10 专业版 Vmware workstation 14.1.1 Ubuntu 16.04.3 ...

  5. DevExtreme学习笔记(一) DataGrid中js分析

    1.overviewjs采用 $(function() { $("#gridContainer").dxDataGrid({ dataSource: { store: { type ...

  6. Detection综述

    4月中旬开始,尝试对目标检测领域做一个了解,看了差不多6-7篇paper,在这里记录一下: 一.Detection简介 人脸检测的目标是找出图像中所有的人脸对应的位置,算法的输出是人脸外接矩形在图像中 ...

  7. node中用的cookie-parser插件设置的max-age,和普通正常设置max-age的计算方式不一样

    在cookie-parser中通过max-age设置的cookie的过期时间是按照毫秒计算的; 在普通设置的时候max-age后面的值是按秒计算的;

  8. 'vue' 不是内部或外部命令

    运用cnpm淘宝镜像安装vue-cli,然后输入vue,显示“'vue' 不是内部或外部命令”,然后百度查找方法,解决办法如下: 虽然电脑是64位的电脑,然后node我也下载安装的是64位,然后,我重 ...

  9. 【前端开发】ES6知识点系统化梳理笔记

    >ES6扩展: #Map和Set是es6标准新增的数据类型 ##Map是key-value(关键字-值),Map允许修改value,不允许修改key,Map支持下标操作 var m = new ...

  10. Vue指令之`v-model`和`双向数据绑定

     v-bind 只能实现数据的单向绑定,从 M 自动绑定到 V, 无法实现数据的双向绑定 <input type="text" v-bind:value="msg& ...