1、迭代
这是我参与更文挑战的第7天,活动详情查看: 更文挑战
1.1 迭代的概念
使用for循环遍历取值的过程叫做迭代,比如:使用for循环遍历列表获取值的过程
1 | python复制代码# Python 中的迭代 |
1.2 可迭代对象
标准概念:在类里面定义
__iter__
方法,并使用该类创建的对象就是可迭代对象简单记忆:使用for循环遍历取值的对象叫做可迭代对象, 比如:列表、元组、字典、集合、range、字符串
1.3 判断对象是否是可迭代对象
1 | python复制代码# 元组,列表,字典,字符串,集合,range都是可迭代对象 |
1.4 自定义可迭代对象
在类中实现
__iter__
方法
自定义可迭代类型代码
1 | python复制代码from collections import Iterable |
执行结果:
1 | python复制代码Traceback (most recent call last): |
通过执行结果可以看出来,遍历可迭代对象依次获取数据需要迭代器
总结
在类里面提供一个__iter__
创建的对象是可迭代对象,可迭代对象是需要迭代器完成数据迭代的
2、迭代器
2.1 自定义迭代器对象
自定义迭代器对象: 在类里面定义
__iter__
和__next__
方法创建的对象就是迭代器对象
1 | python复制代码from collections import Iterable |
运行结果:
1 | python复制代码True |
2.2 iter()函数与next()函数
- iter函数: 获取可迭代对象的迭代器,会调用可迭代对象身上的
__iter__
方法 - next函数: 获取迭代器中下一个值,会调用迭代器对象身上的
__next__
方法
1 | python复制代码# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象 |
2.3 for循环的本质
遍历的是可迭代对象
- for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
遍历的是迭代器
- for item in Iterator 循环的迭代器,不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
2.4 迭代器的应用场景
我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。
举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
现在我们想要通过for…in…循环来遍历迭代斐波那契数列中的前n个数。那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。
1 | python复制代码class Fibonacci(object): |
执行结果:
1 | python复制代码0 |
小结
迭代器的作用就是是记录当前数据的位置以便获取下一个位置的值
3、生成器
3.1 生成器的概念
生成器是一类特殊的迭代器,它不需要再像上面的类一样写
__iter__()和__next__()
方法了, 使用更加方便,它依然可以使用next函数和for循环取值
3.2 创建生成器方法1
- 第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )
1 | python复制代码my_list = [i * 2 for i in range(5)] |
执行结果:
1 | python复制代码[0, 2, 4, 6, 8] |
3.3 创建生成器方法2
在def函数里面看到有yield关键字那么就是生成器
1 | python复制代码def fibonacci(num): |
在使用生成器实现的方式中,我们将原本在迭代器__next__
方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。
简单来说:只要在def中有yield关键字的 就称为 生成器
3.4 生成器使用return关键字
1 | python复制代码def fibonacci(num): |
提示:
- 生成器里面使用return关键字语法上没有问题,但是代码执行到return语句会停止迭代,抛出停止迭代异常
3.5 yield和return的对比
- 使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)
- 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
- 每次启动生成器都会返回一个值,多次启动可以返回多个值,也就是yield可以返回多个值
- return只能返回一次值,代码执行到return语句就停止迭代,抛出停止迭代异常
3.6 使用send方法启动生成器并传参
send方法启动生成器的时候可以传参数
1 | python复制代码def gen(): |
执行结果:
1 | python复制代码In [43]: f = gen() |
**注意:如果第一次启动生成器使用send方法,那么参数只能传入None,一般第一次启动生成器使用next函数
小结
- 生成器创建有两种方式,一般都使用yield关键字方法创建生成器
- yield特点是代码执行到yield会暂停,把结果返回出去,再次启动生成器在暂停的位置继续往下执行
4、协程
4.1 协程的概念
协程,又称微线程,纤程,也称为用户级线程,在不开辟线程的基础上完成多任务,也就是在单线程的情况下完成多任务,多个任务按照一定顺序交替执行 通俗理解只要在def里面只看到一个yield关键字表示就是协程
协程是也是实现多任务的一种方式
协程yield的代码实现
简单实现协程
1 | python复制代码import time |
运行结果:
1 | python复制代码----work1--- |
小结
协程之间执行任务按照一定顺序交替执行
5、greenlet
5.1 greentlet的介绍
为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单
使用如下命令安装greenlet模块:
1 | 复制代码pip3 install greenlet |
使用协程完成多任务
1 | python复制代码import time |
运行效果
1 | python复制代码work1... |
6、gevent
6.1 gevent的介绍
greenlet已经实现了协程,但是这个还要人工切换,这里介绍一个比greenlet更强大而且能够自动切换任务的第三方库,那就是gevent。
gevent内部封装的greenlet,其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO
安装
1 | python复制代码pip3 install gevent |
6.2 gevent的使用
1 | python复制代码import gevent |
运行结果
1 | python复制代码<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 0 |
可以看到,3个greenlet是依次运行而不是交替运行
6.3 gevent切换执行
1 | python复制代码import gevent |
运行结果
1 | python复制代码<Greenlet at 0x7fa70ffa1c30: f(5)> 0 |
6.4 给程序打补丁
1 | python复制代码import gevent |
运行结果
1 | python复制代码work1.... |
6.5 注意
- 当前程序是一个死循环并且还能有耗时操作,就不需要加上join方法了,因为程序需要一直运行不会退出
示例代码
1 | python复制代码import gevent |
执行结果:
1 | erlang复制代码主线程中执行work1....work2....work1....work2....work1....work2....主线程中执行主线程中执行主线程中执行..省略.. |
- 如果使用的协程过多,如果想启动它们就需要一个一个的去使用join()方法去阻塞主线程,这样代码会过于冗余,可以使用gevent.joinall()方法启动需要使用的协程
实例代码
1 | python复制代码 import time |
7、进程、线程、协程对比
7.1 进程、线程、协程之间的关系
- 一个进程至少有一个线程,进程里面可以有多个线程
- 一个线程里面可以有多个协程
7.2 进程、线程、线程的对比
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源最大,效率很低
- 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
- 协程切换任务资源很小,效率高
- 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
小结
- 进程、线程、协程都是可以完成多任务的,可以根据自己实际开发的需要选择使用
- 由于线程、协程需要的资源很少,所以使用线程和协程的几率最大
- 开辟协程需要的资源最少
本文转载自: 掘金