最近在学Python里异步IO中的协程,协程最初是由生成器变形的yield而来,感觉学完理解有些困难,故此再来回顾一下之前学习的生成器。在Python学习之初比较容易混淆可迭代对象、迭代器和生成器。因此,做以总结以辨析其中关键的区别。以下仅为习后个人理解,如有偏差,还请指出!

Talk is cheap,show me the picture!

解释之前,我们先来看一张概览图,下面是一张关于容器(container)、可迭代对象(Iterable)、迭代器(iterator)、生成器(generator)、生成器函数和生成器表达式的概览图。

一、容器(container)
【概述】:容器就是一个用来存储多个元素的数据结构
【特点】:
①容器中的元素可通过迭代获取。
②所有容器中的元素被存储在内存中。
【举例】:以下都是常见容器对象,他们都可以通过迭代获取,所以它们也是可迭代对象(Iterable),我们暂且理解可迭代对象就是可以被迭代获取的对象。(注:并非所有容器对象都是可迭代对象)
首先引入如下模块:

from collections import Iterable # 可迭代对象  现在是 from collections.abc import Iterable

from collections import Iterator # 迭代器

1.列表(list)

title = ['Python','Java','C++']
for i in title: # 通过for循环,迭代获取
print(i)
isinstance(title,Iterable) # True

打印顺序:Python,Java,C++,True

2.元组(tuple)

title = ('Python','Java','C++')
for i in title: # 通过for循环,迭代获取
print(i)
isinstance(title,Iterable) # True

二、可迭代对象(Iterable)

【简述】:可迭代对象就是可以被迭代获取的对象。
【特点】:Iterable定义了可返回迭代器的__iter__()方法。
【举例】:

title = ['Python','Java','C++'] # 列表是一个可迭代对象
isinstance(title,Iterable) # True
a = iter(title) # 由可迭代对象的iter方法返回一个迭代器
>>> next(a)
Python
>>> next(a)
Java
>>> next(a)
C++
>>> next(a) # 抛出StopIteration异常

如果我们写出以下代码:

x = [1, 2, 3]
for elem in x:
...

则实际内部运行过程是这样的,列表x是一个可迭代对象,在for循环中经过iter()方法变为迭代器,然后遍历x实际就是内部调用elem = next(x)

三、迭代器(Iterator)
【简述】:迭代器是一个带状态的对象。之所以说是带状态的对象是因为迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器可以通过next()方法来迭代获取下一个值。
【特点】:
①Iterator实现了__iter__()和__next__()方法。
②迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果(不同于容器)。
【举例】:和上面例子一样,下面的a就是一个迭代器,和可迭代对象title不同,a可以通过next(a)来逐个获取其中的每个元素。

title = ['Python','Java','C++'] # 列表是一个可迭代对象
isinstance(title,Iterable) # True
a = iter(title) # 由可迭代对象的iter方法返回一个迭代器
>>> next(a)
Python
>>> next(a)
Java
>>> next(a)
C++
>>> next(a) # 抛出StopIteration异常

如果使用next(title)则会报TypeError错,显示列表对象不是一个迭代器。

>>> next(title)
TypeError: 'list' object is not an iterator

迭代器每次调用next()方法的时候做两件事:

  1. 为下一次调用next()方法修改状态
  2. 生成当前调用的返回结果

四、生成器(generator)

【概述】:生成器(generator)是一种特殊的迭代器。
【特点】:
①生成器拥有迭代器的迭代传出数据的功能,但用关键字yield来替换迭代器中的__next__()方法来实现,而拥有yield关键字的函数就是生成器函数。
②生成器可以传入数据进行计算(不同于迭代器),并根据变量内容计算结果后返回。
③迭代器不会一次性把所有元素加载到内存,而是调用的时候才生成返回结果(相同于迭代器,不同于容器)。
④可以通过for循环进行迭代(因为生成器是迭代器)
综上所述:生成器是迭代器的衍生物,但迭代器不是生成器,因为迭代器没有传入数据功能。

4.1 生成器函数(generator function)
【概述】:含有yield关键字的函数就是生成器函数。
【举例】:
1.生成器可以通过for循环迭代,这一点和迭代器中的next()函数功能相同,如下我们使用一个有限序列作为例子,如果是无限序列,则会无休止的循环下去

from collections.abc import Iterable,Iterator
def d():
yield 1
yield 2
yield 3
yield 4
for i in d():
print(i)
print (type(d())) # generator
isinstance(d(),Iterator) # True
isinstance(d(),Iterable) # True

得到如下结果:(也可以看出生成器即是迭代器,也是可迭代对象)

2.以下我们展示一个传入和传出数据的例子。

def d():
print('初始化')
sum = 0
value = yield sum
sum = sum + value
print('sum的值是:%d' % sum)
value = yield sum
sum = sum + value
print('sum的值是:%d' % sum)
value = yield sum
sum = sum + value
print('sum的值是:%d' % sum)
return sum+1
c = d() # c是一个生成器,此行代码并不运行d()内容
a = c.send(None) # 启动生成器,遇到d()的第一个yield时中断
print('生成器传出的值:%d' % a)
a = c.send(1)
print('生成器传出的值:%d' % a)
a = c.send(1)
print('生成器传出的值:%d' % a)

yield有中断的功能:

当运行a = c.send(None)时,启动生成器函数,在第一个yield中断,此时这行程序仅仅运行了yield sum并没有开始赋值,而yield sum就相当于return sum,即向函数外传出sum,所以函数外接收值的变量a存储的值是0。

当运行a = c.send(1)时,我们继续启动生成器函数开始运行value = yield,并向生成器函数的第一个中断点yield传递了值1,然后通过yield把1传递给了value并通过后续计算累加sum。程序直到第二个yield中断,向函数外返回第二个sum。以此类推。

所以执行结果如下:

初始化
生成器传出的值:0
sum的值是:1
生成器传出的值:1
sum的值是:2
生成器传出的值:2

【注】:
1)第一个send(None)填入的参数必须是None,因为在启动生成器函数到第一次中断,程序只运行到第一个yield sum,没有赋值语句,所以只能填None。
2)对于生成器函数最后的return sum语句并不向函数外传递sum,而是会在迭代结束时报错StopIteration: 3,返回值sum包含在StopIteration的value中,也就是3,可以捕获StopIteration在函数外得到这个值。把上面代码中的a = c.send(None)之后的代码改成如下代码即可。

while True:
print('生成器传出的值:%d' % a)
try:
a = c.send(1)
except StopIteration as e:
print('生成器传出最后的值:%d' % e.value)
break

如果不加返回值的话直接break就行,没有外循环的话就直接pass

while True:
print('生成器传出的值:%d' % a)
try:
a = c.send(1)
except StopIteration:
break

4.2 生成器表达式(generator expression)

生成器表达式是列表生成式的生成器版本,看起来像列表生成式,但是它返回的是一个生成器对象而不是列表对象。当然,它既然是生成器,也就可以上个例子一样通过send()函数来迭代。

print ([x*x for x in range(10)])

print  (x*x for x in range(10)) # 生成器表达式

输出结果:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000000000214B570>

五、协程

下面是一段协程的代码

def simple_coro2(a):
print("-> Started: a =", a)
b = yield a
print("-> Received: b: =", b)
c = yield a + b
print("-> Received: c=", c)

可以看到在生成器中,每次调用send()方法时,yield语句对外返回yield右侧的值,再将输入的值赋值给左侧的变量。所以协程在yield上做了拓展。能接收一些参数。

六、协程(coroutine)和生成器(generator)的区别

协程和生成器都使用yield关键字。但是协程更像是对生成器语法的一些扩展

  • 在生成器中, yield 只对外产出值
  • 在协程中,yield能对外产出值,而且能接收通过send()方法传入值
  • 协程有四个状态

    • GEN_CREATED
    • GEN_RUNNING
    • GEN_SUSPEND
    • GEN_CLOSED

    每个协程在使用时,必须进行初始化。否则抛出异常

    TypeError: can't send non-None value to a just-started generator

如你所见,下面这代码将定义一个生成器的。

import time

def eat():
while True:
if food:
print("小明 吃完{}了".format(food))
yield
print("小明 要开始吃{}...".format(food))
time.sleep(1) food = None
MING = eat() # 产生一个生成器
MING.send(None) # 预激
food = "面包"
MING.send('面包')
MING.send('苹果')
MING.send('香肠')

运行一下,从结果中可以看出,不管我们塞给小明什么东西,小明都将只能将他们当成面包吃。

小明 要开始吃面包...
小明 吃完面包了
小明 要开始吃面包...
小明 吃完面包了
小明 要开始吃面包...
小明 吃完面包了

那再来看一下协程的。

import time

def eat():
food = None
while True:
if food:
print("小明 吃完{}了".format(food))
food = yield
print("小明 开始吃{}...".format(food))
time.sleep(1) MING = eat() # 产生一个生成器
MING.send(None) # 预激
MING.send('面包')
MING.send('苹果')
MING.send('香肠')

运行一下,从结果中可以看出,小明已经可以感知我们塞给他的是什么食物。

小明 开始吃面包...
小明 吃完面包了
小明 开始吃苹果...
小明 吃完苹果了
小明 开始吃香肠...
小明 吃完香肠了

仔细观察一下,上面两段代码并没有太大的区别,我们将主要关注点集中在 yidld 关键词上。

可以发现,生成器里 yield 左边并没有变量,而在协程里,yield 左边有一个变量。

在函数被调用后,一个生成器就产生了,而一般的生成器不能再往生成器内部传递参数了,而这个当生成器里的 yield 左边有变量时,就不一样了,它仍然可以在外部接收新的参数。这就是生成器与协程的最大区别。

协程的优点:

  • 线程属于系统级别调度,而协程是程序员级别的调度。使用协程避免了无意义的调度,减少了线程上下文切换的开销,由此可以提高性能。
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型

协程的缺点:

  (1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

  (2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。

总结:

编一则小故事:
在Python中有一种可迭代对象(Iterable),他声称自己可以通过迭代来获取值(如for循环),但怎么迭代他说那是迭代器(Iterator)的事,他只负责把自己变成迭代器。于是当他揽下一个需要迭代的活时,他就通过Iter()把自己变成了迭代器。

另外一个容器兄弟开始呼应:”我就是可迭代对象呢,我包含常见的列表、元组、字典、集合和字符串,我将这些序列存储在内存中,需要的时候可以一并取出“

迭代器有点不屑,开始怼容器说:“你将所有序列都存储在内存中,对于少量有限序列是可以的,那如果是大量序列或无限序列,内存都让给你存岂不是很败家?我是迭代功能的实干家,我可以通过next()进行迭代,但并不是把所有序列放在内存中再迭代取值,而是仅仅将迭代到的某个值取到内存中,做到按需存储。

生成器对迭代器说:“我就是另一个你,但我们不一样。大兄弟你的确是个实干家,但你仅仅只能迭代取出数据,而我除了有你的功能还可以通过send()传入数据,传入的数据可在生成器内进行计算呢。”

最后,生成器和迭代器和容器说:“我们都是可迭代对象,Iterable对于我们来说更像一种特点,表示我们是可迭代的,但是功能的实现是我们自己完成的”

http://python-online.cn/zh_CN/latest/c02/c02_12.html

https://jianshu.com/p/5103c6a63e33
https://blog.csdn.net/SL_World/article/details/86507872

https://segmentfault.com/a/1190000013460584

https://www.jianshu.com/p/a36aa573f954

linux中的ll(转)的更多相关文章

  1. 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制

    参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...

  2. Linux中find常见用法示例

    ·find   path   -option   [   -print ]   [ -exec   -ok   command ]   {} \; find命令的参数: pathname: find命 ...

  3. Linux中检索文件

    1 , Use locate command It is a fast way to find the files location, but if a file just created ,it w ...

  4. 如何在Linux中搭建禅道8.4.1(httpd+php+mysql)

    1.安装httpd 命令:yum install httpd 然后一路y即可 2.安装php 命令:yum install php   3.安装php-mysql 命令:yum install php ...

  5. Linux中的用户和用户组

      在Linux中,有三种用户: Root 用户:也称为超级用户,对系统拥有完全的控制权限.超级用户可以不受限制的运行任何命令.Root 用户可以看做是系统管理员. 系统用户:系统用户是Linux运行 ...

  6. linux中shell变量$#,$@,$0,$1,$2的含义解释

    linux中shell变量$#,$@,$0,$1,$2的含义解释: 变量说明: $$ Shell本身的PID(ProcessID) $! Shell最后运行的后台Process的PID $? 最后运行 ...

  7. 在linux中设置静态ip地址

    在linux中设置静态ip地址1.在终端中输入:vi /etc/sysconfig/network-scripts/ifcfg-eth0 2.开始编辑,填写ip地址.子网掩码.网关.DNS等[root ...

  8. windows和linux中搭建python集成开发环境IDE——如何设置多个python环境

    本系列分为两篇: 1.[转]windows和linux中搭建python集成开发环境IDE 2.[转]linux和windows下安装python集成开发环境及其python包 3.windows和l ...

  9. linux 中部署ant编译的包中缺少问题

    今天遇到在window上部署ant编译的包,能运行正常,但部署在linux中出现跳不进jsp中,出现404问题,后来经过排查在jsp中<%@taglib prefix="c" ...

  10. 在Linux中运行Nancy应用程序

    最近在研究如何将.NET应用程序移植到非Windows操作系统中运行,逐渐会写一些文章出来.目前还没有太深的研究,所以这些文章大多主要是记录我的一些实验. 这篇文章记录了我如何利用NancyFx编写一 ...

随机推荐

  1. 使用 Feed43

    1.打开 Feed43 2.将标题.链接,时间等变化的字段删去用 {%} 代替.将固定且多余的字段删去用 {*} 代替.注意,源码中有换行的地方均需要添加{*} . 3.活学活用

  2. PHP-线程安全与非线程安全版本的区别

    Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍. ...

  3. Tomcat JNDI + spring配置

    http://hi.baidu.com/lzpsky/item/f9a727ba823257eb4ec7fd27 一.简介 JNDI : Java Naming and Directory Inter ...

  4. Linux命令-查看进程命令:pstree

    查看进程树,ps aux查看进程,如果进程太多看起来很不方便,可以使用pstree以树形方式显示正在运行的所有进程 pstree -p 查看进程树 还是太多了,可以使用管道符进行查找httpd(apa ...

  5. python学习笔记013——模块中的私有属性

    1 私有属性的使用方式 在python中,没有类似private之类的关键字来声明私有方法或属性.若要声明其私有属性,语法规则为: 属性前加双下划线,属性后不加(双)下划线,如将属性name私有化,则 ...

  6. Ubuntu 10.04 安装流程

    ubuntu 10.04 安装流程   需安装libxrender-dev才能跑html5           来自为知笔记(Wiz)

  7. Openresty增加waf配置

    Openresty增加waf配置 1. Ngx lua waf 说明 防止sql注入,本地包含,部分溢出,fuzzing测试,xss,SSRF等web攻击 防止svn/备份之类文件泄漏 防止Apach ...

  8. VS2012开发cocos游戏遇到问题汇总

    1.编译成android时.须要改动jni/android.mk,每一个cpp都改动一下太麻烦,能够让他自己主动识别. # 遍历文件夹及子文件夹的函数 define walk $(wildcard $ ...

  9. Html中meta标签详解--以前经常忽略的

    W3School介绍:http://www.w3school.com.cn/html5/html5_meta.asp meta是用来在HTML文档中模拟HTTP协议的响应头报文. meta 标签的用处 ...

  10. 统计svn 代码提交情况

    统计svn代码提交,使用工具 statsvn.jar 下载地址:http://sourceforge.net/projects/statsvn/ rem 声明一个时间变量 作为文件名 %time:~, ...