python中函数和生成器的运行原理
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author:love_cat # python的函数是如何工作的 # 比方说我们定义了两个函数 def foo():
bar() def bar():
pass # 首先python解释器(python.exe)会用一个叫做PyEval_EvalFrameEx()的C语言函数去执行foo,所以python的代码是运行在C程序之上的
# 当运行foo函数时,会首先创建一个栈帧(stack frame),表示函数调用栈当中的某一帧,相当于一个上下文,函数要在对应的栈帧上运行。
# 正所谓python一切皆对象,栈帧也是一个对象
# python虽然是解释型语言,但在解释之前也要进行一次预编译,编译成字节码对象,然后在对应的栈帧当中运行 # 关于python的编译过程,我们可以是dis模块查看编译后的字节码是什么样子
import dis
print(dis.dis(foo))
# 程序运行结果
'''
11 0 LOAD_GLOBAL 0 (bar)
2 CALL_FUNCTION 0
4 POP_TOP
6 LOAD_CONST 0 (None)
8 RETURN_VALUE
None
'''
# 首先LOAD_GLOBAL,把bar这个函数给load进来
# 然后CALL_FUNCTION,调用bar函数的字节码
# POP_POP,从栈的顶端把元素打印出来
# LOAD_CONST,我们这里没有return,所以会把None给load进来
# RETURN_VALUE,把None给返回
'''
以上是字节码的执行过程
''' # 过程就是:
'''
1.先预编译,得到字节码对象
2.python解释器去解释字节码
3.当解释到foo函数的字节码时,会为其创建一个栈帧
4.然后调用C函数PyEval_EvalFrameEx()在foo对应的栈帧上执行foo的字节码,参数就是foo对应的栈帧对象
5.当遇到CALL_FUNCTION,也就是在foo中执行到bar的字节码时,会继续为其创建一个栈帧
6.然后把控制权交给新创建的栈帧对象,在bar对应的栈帧中运行bar的字节码
''' # 我们看到目前已经有两个栈帧了,这不是关键。关键所有的栈帧都分配在堆的内存上,而不是栈的内存上
# 堆内存有一个特点,如果你不去释放,那么它就一直待在那儿。这就决定了栈帧可以独立于调用者存在
# 即便调用者不存在,或者函数退出了也没有关系,因为它始终在内存当中。只要有指针指向它,我们就可以对它进行控制
# 这个特性决定了我们对函数的控制会相当精确。
# 我们可以改写这个函数
# 在此之前,我们要引用一个模块inspect,可以获取栈帧
import inspect frame = None
def foo():
bar() def bar():
global frame
frame = inspect.currentframe() # 将获取到的栈帧对象赋给全局变量 foo()
# 此时函数执行完毕,但是我们依然可以拿到栈帧对象
# 栈帧对象一般有三个属性
# 1.f_back,当前栈帧的上一级栈帧
# 2.f_code,当前栈帧对应的字节码
# 3.f_locals,当前栈帧所用的局部变量 print(frame.f_code)
print(frame.f_code.co_name)
'''
<code object bar at 0x000000000298C300>
bar
'''
# 可以看出,打印的是我们bar这个栈帧 # 之前说过,栈帧可以独立于调用方而存在
# 我们也可以拿到foo的栈帧,也就是bar栈帧的上一级栈帧
foo_frame = frame.f_back
print(foo_frame.f_code)
print(foo_frame.f_code.co_name)
'''
<code object foo at 0x000000000239C8A0>
foo
'''
# 我们依然可以拿到foo的栈帧 # 总结一下:就是有点像递归。遇见新的调用,便创建一个新的栈帧,一层层地创建,然后一层层地返回
关于类似于递归这个现象,我们可以看一张图
生成器的运行原理
# 我们之前说了,栈帧是分配在堆内存上的
# 正是因为如此,生成器才有实现的可能 # 我们定义一个生成器
def gen_func():
yield 123
name = "satori"
yield 456
age = 18
return "i love satori" # 注意在早期的版本中生成器是不允许有返回值的,但在后来的版本中,允许生成器具有返回值 # python解释之前,也进行预编译,在编译的过程中,发现有yield,就已经被标记为生成器了
如何实现的呢?实际上是对PyFrameObject做了一层封装
def gen_func():
yield 123
name = "satori"
yield 456
age = 18
return "i love satori" import dis
gen = gen_func()
print(dis.dis(gen))
'''
10 0 LOAD_CONST 1 (123)
2 YIELD_VALUE
4 POP_TOP 11 6 LOAD_CONST 2 ('satori')
8 STORE_FAST 0 (name) 12 10 LOAD_CONST 3 (456)
12 YIELD_VALUE
14 POP_TOP 13 16 LOAD_CONST 4 (18)
18 STORE_FAST 1 (age) 14 20 LOAD_CONST 5 ('i love satori')
22 RETURN_VALUE
None
''' # 可以看到,结果中有两个yield,因为我们的函数中有两个yield
# 最后的LOAD_CONST后面的('i love satori'),表示我们的返回值
# 最后RETURN_VALUE # 前面的图也解释了,gi_frame的f_lasti会记录最近的一次执行状态,gi_locals会记录当前的局部变量
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
'''
-1
{}
'''
# 我们创建了生成器,但是还没有执行,所以值为-1,当前局部变量也为空 # 我们next一下
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
'''
2
{}
'''
# 我们发现数字是2,所以指向第二行,YIELD_VALUE,yield的值就是123
# 此时局部变量依旧为空 # 继续next,会执行到第二个yield的位置
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
'''
12
{'name': 'satori'}
'''
# 数字是12,所以指向第十二行,第二个YIELD_VALUE,yield的值就是456
# 此时name="satori",被添加到了局部变量当中 # 因此到这里便更容易理解了,为什么生成器可以实现了。
# 因为PyGenObject对函数的暂停和前进,进行了完美的监督,有变量保存我最近一行代码执行到什么位置
# 再通过yield来暂停它,就实现了我们的生成器 # 跟函数一样,我们的生成器对象也是分配在堆内存当中的,可以像函数的栈帧一样,独立于调用者而存在
# 我们可以在任何地方去调用它,只要我们拿到这个栈帧对象,就可以控制它继续往前走
# 正是因为可以在任何地方控制它,才会有了协程这个概念,这是协程能够实现的理论基础
# 因为有了f_lasti,生成器知道下次会在什么地方执行,不像函数,必须要一次性运行完毕
# 以上就是生成器的运行原理
python中函数和生成器的运行原理的更多相关文章
- python中的函数、生成器的工作原理
1.python中函数的工作原理 def foo(): bar() def bar(): pass python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c ...
- python中函数的参数
函数参数(一) 思考一个问题,如下: 现在需要定义一个函数,这个函数能够完成2个数的加法运算,并且把结果打印出来,该怎样设计?下面的代码可以吗?有什么缺陷吗? def add2num(): a = 1 ...
- Python中函数参数传递问题【转】
1. Python passes everything the same way, but calling it "by value" or "by reference& ...
- 对Python中函数参数类型及排序问题,三个方面的总结
Python中函数的参数问题有点复杂,主要是因为参数类型问题导致的情况比较多,下面来分析一下. 参数类型:缺省参数,关键字参数,不定长位置参数,不定长关键字参数. 其实总共可以分为 位置参数和关键字参 ...
- 深入理解python中函数传递参数是值传递还是引用传递
深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用 ...
- 讲讲python中函数的参数
python中函数的参数 形参:定义函数时代表函数的形式参数 实参:调用函数时传入的实际参数 列如: def f(x,y): # x,y形参 print(x, y) f(1, 2) # 1, 2 实参 ...
- python 中函数的参数
一.python中的函数参数形式 python中函数一般有四种表现形式: 1.def function(arg1, arg2, arg3...) 这种是python中最常见的一中函数参数定义形式,函数 ...
- python 中函数参数传递形式
python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要 ...
- python中函数的参数解析
python中函数的各种参数梳理: 1.形参:函数定义时传入的参数 2.实参:函数调用时传入的参数 (有形参必传实参,形参里自身特点可不传的,可传可不传) 3.缺省参数:不传为默认值,传了会覆盖(下面 ...
随机推荐
- storm集群安装部署
安装步骤: 搭建Zookeeper集群: 安装Storm依赖库: 下载并解压Storm发布版本: 修改storm.yaml配置文件: 启动Storm各个后台进程. 1. 搭建Zookeeper集群 这 ...
- cocos2d-x 3.0的入门程序:helloworld
看过了这么多不同方向的应用,发现很多程序入门都是helloworldhelloworld是所有程序员的绝对初恋 先看一下程序的运行结果吧 然后就是他的工程代码 工程的目录有两个 Classes:程序中 ...
- 剑指Offer - 九度1351 - 数组中只出现一次的数字
剑指Offer - 九度1351 - 数组中只出现一次的数字2013-11-23 01:23 题目描述: 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. ...
- PhpStorm 格式化中花括号缩进方式
格式化前: function func() { return ; } 格式化后: function func() { return ; } 在PhpStorm中的配置:File -> Setti ...
- [Ceres]C++优化库
官网教程: http://ceres-solver.org/nnls_tutorial.html 定义了一个最小二乘法求解器 自动求导的功能
- CSU-1986 玄学
题目链接 http://acm.csu.edu.cn:20080/csuoj/problemset/problem?pid=1986 题目 Description 阴阳师子浩君,最近从<初等数论 ...
- JAVA相似算法的运用
今天要处理问题是把一个产品的名字按照其内容对比文档转换出它的中文名. 但是这个文档感觉不全,产品种类有多又杂. 如果像这样写的话 if(xxx.contains()) else if() ... 不知 ...
- 微信小程序--动态添加class样式
尺寸单位: rpx(responsive pixel): 可以根据屏幕宽度进行自适应.规定屏幕宽为750rpx.如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = ...
- 软工实践Alpha冲刺(7/10)
队名:起床一起肝活队 组长博客:博客链接 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去两天完成了哪些任务 描述: 已经解决登录注册等基本功能的界面. 完成非功能的主界面制作 ...
- android自定义控件属性
有两种方法为自定义的控件设置属性 . 来自为知笔记(Wiz)