可迭代对象与迭代器
关系:迭代器一定是可迭代对象,可迭代对象不一定是迭代器。
如何判断:
可迭代对象:在Python中如果有一个对象有__iter__()方法,则称这个对象是可迭代的(Iterable)。
迭代器:在Python中如果有一个对象有__iter__()方法和__next__()方法,则称这个对象是迭代器(Iterator)。
代码示例:
1 from collections import Iterable
2 from collections import Iterator
3 import time
4
5 class Classmate(object):
6 def __init__(self):
7 self.names = list()
8
9 def add(self,name):
10 self.names.append(name)
11
12 def __iter__(self):
13 """如果想要一个对象称为一个 可以迭代的对象,即可以使用for,那么必须实现__it er__方法"""
14 return ClassIterator()
15
16
17 class ClassIterator(object):
18 def __iter__(self):
19 pass
20 def __next__(self):
21 return 11
22
23
24 classmate = Classmate()
25 classmate.add("老王")
26 classmate.add("王二")
27 classmate.add("张三")
28
29 print("判断classmate是否可以迭代的对象:", isinstance(classmate,Iterable))
30 classmate_iterator = iter(classmate)
31 print("判断classmate_iterator是否可以迭代的对象:", isinstance(classmate_iterator,Iterator ))
32
33 print(next(classmate_iterator))
迭代器的应用场景
迭代器的最核心功能就是可以通过__next__()函数的调用来返回下一个数据值,如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间
举个例子,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
0,1,1,2,3,5,8,13,21,34,…
现在我们想要通过for…in…循环来遍历迭代斐波那契数列中的前n个数,那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。
1 class Fibonacci(object):
2 def __init__(self,all_num):
3 self.all_num = all_num
4 self.current_num = 0
5 self.a = 0
6 self.b = 1
7
8 def __iter__(self):
9 return self
10
11 def __next__(self):
12 if self.current_num < self.all_num:
13 ret = self.a
14
15 self.a,self.b = self.b,self.a+self.b
16 self.current_num += 1
17
18 return ret
19 else:
20 raise StopIteration
21 fibo = Fibonacci(10)
22
23 for num in fibo:
24 print(num)
并不是只有for循环能接受可迭代对象
除了for循环能接受可迭代对象,list,tuple等也能接受。
li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)
2
生成器
利用迭代器,我们可以在每次迭代获取(通过next()方法)时,按照特定的规律进行生成,但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们最近纪律,进而才能根据当前状态生成下一个数据,为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器
创建生成器方法1
要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成时的[]改成()
创建L和G的区别仅在于最外层的[]和(),L是一个列表,而G是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数,for循坏,list()等方法使用。
创建生成器方法2
generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
代码示例:
运用for循环实现
1 def create_num(all_num):
2 a, b = 0, 1
3 current_num = 0
4 while current_num < all_num:
5 yield a # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器> 模板
6 a, b = b, a+b
7 current_num += 1
8
9 # 如果在调用create_num的时候,发现这个函数中有yield,那么此时,不是调用函数,而是创建一个生
成器对象
10 obj = create_num(10)
11
12 for num in obj:
13 print(num)
运用next()函数实现
1 def create_num(all_num):
2 a, b = 0, 1
3 current_num = 0
4 while current_num < all_num:
5 yield a # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器> 模板
6 a, b = b, a+b
7 current_num += 1
8 return "over...."
9 obj = create_num(10)
10
11 while True:
12 try:
13 ret = next(obj)
14 print(ret)
15 except Exception as ret:
16 print(ret.value)
17 break
总结
- 使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)
- yield关键字有两点作用:
- 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
- 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用 - 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
- python3中的生成器可以使用return返回最终运行的返回值,而Python2中生成器不允许使用return返回一个返回值(即可以使用return从生成器中退出,但return后不能有任何表达式)
使用send唤醒
我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。
1 def create_num(all_num):
2 a, b = 0, 1
3 current_num = 0
4 while current_num < all_num:
5 ret = yield a
6 print("ret>>>>", ret)
7 a, b = b, a+b
8 current_num += 1
9 obj = create_num(10)
10
11 ret = next(obj)
12 print(ret)
13
14 ret = obj.send('我可以传参数')
15 print(ret)
注:
如果想用到send,请不要放在第一次生成的时候,否则程序会报错(解决办法:xxx.send(None));一般来说,第一次使用next()唤醒,之后再用send传一个附加数据。
总结:
迭代器:减少内存空间,可以实现循环
生成器:能够让看上去是函数暂停执行(断点)且也能附加一个数据,可以通过next(),send()唤醒继续执行。
使用yield完成多任务(了解)
因为比较简单,所以直接代码示例
1 import time
2
3
4 def task_1():
5 while True:
6 print("---1----")
7 time.sleep(0.1)
8 yield
9 def task_2():
10 while True:
11 print("----2----")
12 time.sleep(0.1)
13 yield
14
15 def main():
16 t1 = task_1()
17 t2 = task_2()
18 while True:
19 next(t1)
20 next(t2)
21
22
23 if __name__ == "__main__":
24 main()
greenlet
为了更好使用协程来完成多任务,Python中的greenlet模块对其封装,从而使得切换任务变得更加简单。
安装方式
使用如下命令安装greenlet模块:
sudo pip3 install greenlet
from greenlet import greenlet
import time
def test1():
while True:
print("----A----")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("----B----")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 切换到grl中运行
gr1.switch()
gevent
greenlet已经实现了协程,但是这个还是人工切换,是不是觉得太麻烦了,Python还有一个比greenlet更强大的并且能够自动切换任务的模块:gevent
其原理是当一个greenlet遇到IO(input output)操作时,比如访问网络,就是自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO
安装
pip3 install gevent
gevent的使用(常用)
1 import gevent
2
3 def f(n):
4 for i in range(n):
5 print(gevent.getcurrent(), i)
6 gevent.sleep(1)
7 g1 = gevent.spawn(f, 5)
8 g2 = gevent.spawn(f, 5)
9 g3 = gevent.spawn(f, 5)
10 g1.join()
11 g2.join()
12 g3.join()
注
由于gevent模块只识别gevent模块中的方法,即耗时只识别:gevent.sleep(1),而不是time.sleep(1)。为避免大量修改程序,有以下解决办法
给程序打补丁
from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作代码,换为gevent中自己实现的模块
def coroutine_work(corroutine_name):
for i in range(10):
print(coroutine_name, i)
time.sleep(random.random())
gevent.joinall([gevent.spawn(coroutine_work, "work1"), gevent.spawn(coroutine_work, "work2")])
进程、线程、协程对比
通俗描述
- 有一个老板想要开个工厂进行生产某件商品(例如剪子)
- 他需要花一些财力物力制作一条生长线,这个生产线上有很多器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为: 进程
- 只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
- 这个老板为了提高生产率,想到3种办法:
1.在这条生产线上多招些工人,一起来做剪子,这样效率是成倍增长,即 单进程 多线程 方式
2. 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即 多进程 多线程方式
3. 老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程又多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或在等待某些条件(比如等待另一个工人生产完某道工序,之后他才能再次工作),那么这个员工就利用这个时间去做其他事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其他事情,其他就是:协程方式
简单总结
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很大,效率很低
- 线程切换需要的资源一般,效率一般(当然了在不考虑GIL情况下)
- 协程切换任务资源很小,效率高
- 多进程,多线程根据CPU核数不一样可能是并行的,但是协程是在一个线程中,所以是并发的