#!/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中函数和生成器的运行原理的更多相关文章

  1. python中的函数、生成器的工作原理

    1.python中函数的工作原理 def foo(): bar() def bar(): pass python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c ...

  2. python中函数的参数

    函数参数(一) 思考一个问题,如下: 现在需要定义一个函数,这个函数能够完成2个数的加法运算,并且把结果打印出来,该怎样设计?下面的代码可以吗?有什么缺陷吗? def add2num(): a = 1 ...

  3. Python中函数参数传递问题【转】

    1. Python passes everything the same way, but calling it "by value" or "by reference& ...

  4. 对Python中函数参数类型及排序问题,三个方面的总结

    Python中函数的参数问题有点复杂,主要是因为参数类型问题导致的情况比较多,下面来分析一下. 参数类型:缺省参数,关键字参数,不定长位置参数,不定长关键字参数. 其实总共可以分为 位置参数和关键字参 ...

  5. 深入理解python中函数传递参数是值传递还是引用传递

    深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用 ...

  6. 讲讲python中函数的参数

    python中函数的参数 形参:定义函数时代表函数的形式参数 实参:调用函数时传入的实际参数 列如: def f(x,y): # x,y形参 print(x, y) f(1, 2) # 1, 2 实参 ...

  7. python 中函数的参数

    一.python中的函数参数形式 python中函数一般有四种表现形式: 1.def function(arg1, arg2, arg3...) 这种是python中最常见的一中函数参数定义形式,函数 ...

  8. python 中函数参数传递形式

    python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要 ...

  9. python中函数的参数解析

    python中函数的各种参数梳理: 1.形参:函数定义时传入的参数 2.实参:函数调用时传入的参数 (有形参必传实参,形参里自身特点可不传的,可传可不传) 3.缺省参数:不传为默认值,传了会覆盖(下面 ...

随机推荐

  1. Java语言基础---两变量间的交换

    使用中间变量交换两个变量的值 int a = 10 , b = 11 , m; m = a; a = b; b = m; 不使用中间变量交换两个变量的值 int a = 10; int b = 11; ...

  2. app图标

    1.http://www.iconfont.cn/ 在里面可以搜索你想要的图标: 比如关闭. 2.选择一个好看的下载png 3.打开ps,ctrl n新建一个图层. 把你下载的png弄到上面,然后关闭 ...

  3. VS Extension+NVelocity系列(三)——让VS支持 NVelocity的智能提示(中)

    一.定义 我们知道,我们的插件是服务于NVelocity的,在你的项目当中,对于NVelocity的模板应当有一个统一的文件扩展名,以便于VS在打开指定扩展名的文件后,就能起到具体的作用. 如果我没有 ...

  4. fix34

    public int[] fix34(int[] nums) { int i3=0; int i4=0; int temp=0; while( (i3<nums.length)&& ...

  5. 《数据结构与算法分析:C语言描述》复习——第四章“树”——二叉树

    2014.06.14 22:49 简介: 二叉树是学习树结构时接触的第一个概念,其他衍生的表示形式包括N叉树(随便多少叉).二叉链表(土话也叫左孩子右兄弟).由于单纯的二叉树是无序的,能做的事情不太多 ...

  6. Caliburn micro 学习笔记...

    页面跳转 LLS 结合 CM 使用方法 事件处理

  7. 如何将现有的项目添加到远程的git库里面!

    我们经常都会遇到这样的场景,就是将本地的一个项目同步到网上远程的git库里面.最近也遇到这样的问题,发现网上很少人讲到这个问题,但是这个问题是很多程序员遇到的版本库管理的最早的拦路虎. 我的远程是ht ...

  8. Android之SeekBar总结(一)

    2015-04-24 SeekBar: 一种特殊的进度条,包含一个滑块用于调节进度值. API 中目录结构如下: 包含几种特殊的属性: 1: max:设置进度条的最大值 .对应方法:setMax(in ...

  9. PBFT性能会下降? 各种算法的对比。

    PBFT协议在超过100个节点的时候性能会下降 作者:maxdeath 链接:https://www.zhihu.com/question/60058591/answer/173970031 首先要搞 ...

  10. Ipython\Jupyter数据分析工具

    使用Python进行数据分析优点 1 Python大量的库为数据分析和处理提供了完整的工具集 2 比起R和Matlab等其他主要用于数据分析的编程语言,Python更全能 3 Python库一直在增加 ...