目录
1. 迭代器
1.1 迭代器应用:自定义列表
?1.2 迭代器应用:斐波那契数列
2. 生成器
?2.1 生成器案例:斐波那契数列
2.2 生成器注意事项
3. 协程
3.1 yield 简单实现协程
3.2 greenlet实现协程
3.3 gevent实现协程
4. 案例:协程实现并发下载器
1. 迭代器
迭代器是一个可以记住遍历位置的对象。可以遍历诸如列表,字典及字符串等序列对象
迭代器的特点:
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
- 迭代器只能往前不会后退
?序列对象可以利用iter() 直接创建迭代器,并通过next() 获取迭代器中的下一个元素。
1.1 迭代器应用:自定义列表
# 自定义列表类
class MyList(object):
def __init__(self):
# 定义实例属性来保存数据
self.items = []
def __iter__(self): # 迭代器方法
# 创建MyListIterator对象
myListIterator = MyListIterator(self.items)
return myListIterator
def addItem(self, element): # 添加数据的方法
self.items.append(element)
# 通用迭代器类
class MyListIterator(object):
def __init__(self, items):
# 定义实例属性
self.items = items # self.items用于保存MyList类传递过来的items
self.current_index = 0 # 记录迭代器当前迭代的位置
def __iter__(self):
pass
def __next__(self): # 获取下一个元素值的方法
# 判断当前的下标是否越界
if self.current_index < len(self.items): # 下标不越界
# 根据下标获取列表中对应的值
data = self.items[self.current_index]
# 下标索引+1
self.current_index += 1
# 返回下标对应的数据
return data
else: # 下标越界
raise StopIteration # raise用于主动抛出异常,StopIteration表示停止迭代
# 如果越界,则抛出异常
if __name__ == '__main__':
# 创建自定义列表对象
myList = MyList()
# 添加数据
myList.addItem("David")
myList.addItem("Tom")
myList.addItem("Flynt")
# for value in myList:
# print(value)
myList_iterator = iter(myList)
print(myList_iterator)
value1 = next(myList_iterator)
value2 = next(myList_iterator)
print(value1)
print(value2)
结果:
?1.2 迭代器应用:斐波那契数列
????????我们发现迭代器最核心的功能就是可以通过next)函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。
????????举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
class Fibnacci(object):
def __init__(self, num): # num表示斐波那契数列的元素数量
self.num = num
self.current_index = 0
# 定义斐波那契数列的第一、二个元素
self.a = 1
self.b = 1
def __iter__(self):
return self
def __next__(self):
# 判断列数是否超过生成的总列数
if self.current_index < self.num: # 未超出范围
data = self.a
self.a, self.b = self.b, self.a + self.b
self.current_index += 1
return data
else: # 超出范围
raise StopIteration # 主动抛出异常
if __name__ == '__main__':
# 创建迭代器对象
fib_iterator = Fibnacci(5)
# for i in range(5):
# value = next(fib_iterator)
# print(value)
for value in fib_iterator:
print(value)
?结果:
2. 生成器
????????利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。
# 列表推导式
data_list = [x for x in range(10)]
# 创建生成器 方式1
data_list2 = (x for x in range(10))
print(data_list2)
print(next(data_list2)) # 通过next()获取下一个值
print(next(data_list2))
# 创建生成器 方式2
def test():
yield 10
n = test()
print(n)
print(next(n))
结果:
?2.1 生成器案例:斐波那契数列
def fibnacci(n):
# 定义变量用于表示斐波那契数列的第一、二个值
a = 1
b = 1
# 定义变量用于表示当前生成的位置
current_index = 0
while current_index < n:
data = a
a, b = b, a + b
current_index += 1
yield data
if __name__ == '__main__':
fib = fibnacci(5)
for i in range(5):
print(next(fib))
2.2 生成器注意事项
????????1.可以使用?return?结束生成器
def fibnacci(n):
# 定义变量用于表示斐波那契数列的第一、二个值
a = 1
b = 1
# 定义变量用于表示当前生成的位置
current_index = 0
while current_index < n:
data = a
a, b = b, a + b
current_index += 1
yield data
return "over"
if __name__ == '__main__':
fib = fibnacci(5)
for i in range(5):
try:
print(next(fib))
except Exception as e:
print(e)
结果:
? ? ? ? ?2.可以使用 send() 方法向生成器传值
def fibnacci(n):
# 定义变量用于表示斐波那契数列的第一、二个值
a = 1
b = 1
# 定义变量用于表示当前生成的位置
current_index = 0
while current_index < n:
data = a
a, b = b, a + b
current_index += 1
sendData = yield data
if sendData == "over":
return "over"
if __name__ == '__main__':
fib = fibnacci(5)
try:
value1 = next(fib)
print(value1)
value2 = next(fib)
print(value2)
value3 = next(fib)
print(value3)
value4 = fib.send("over")
print(value4)
value5 = next(fib)
print(value5)
except Exception as e:
print(e)
结果:
3. 协程
????????协程,又称微线程,纤程,英文名Coroutine 。从技术的角度来说协程就是你可以暂停执行的函数,如果你把它理解成就像生成器一样,那么你就想对了。
????????线程和进程的操作是由程序触发系统接口,最后的执行者是系统,协程的操作则是程序员。
????????协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程则只使用一个线程(单线程),在一个线程中规定某个代码块顺序执行。协程可以使得程序在不开辟新的线程基础上实现多任务。
? ? ? ? 协程的使用场景:当程序中存在大量不需要CPU的操作时(IO)适用于协程。
? ? ? ? 协程通俗的理解:协程可以让一个线程中某个函数,在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数执行。注意不是通过调用函数的方式得到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
? ? ? ? 协程和线程的差异:在实现多任务时,线程切换从系统层面远不止保存和恢复CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能,但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。
3.1 yield 简单实现协程
import time
# 创建 work1 的生成器
def work1():
i = 0
while True:
print("正在执行work1... %d" % i)
yield i
i += 1
time.sleep(1)
def work2():
i = 0
while True:
print("正在执行work2... %d" % i)
yield i
i += 1
if __name__ == '__main__':
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
结果:
????????可以看到的效果是:程序先执行work1,当work1处于休眠时,CPU直接执行work2,因此打印的结果可以看到work1与work2的打印几乎是同时的,但是执行先后顺序一定是work1先执行,work2后执行(如果使用多线程分别实现这两个work方法的话,work2是不会等work1执行完再执行的)。
3.2 greenlet实现协程
????????Greenlet是python的一个C扩展,来源于Stackless python ,旨在提供可自行调度的微线程,即协程。generator实现的协程在yield value时只能将value返回给调用者(caller)。而在greenlet中, target.switch (value)可以切换到指定的协程(target),然后yield value。?greenlet用switch来表示协程的切换,从一个协程切换到另一个协程需要显式指定。
import time
from greenlet import greenlet
def work1():
i = 0
while True:
print("正在执行work1... %d" % i)
i += 1
time.sleep(1)
# 切换到第二个任务
g2.switch()
def work2():
i = 0
while True:
print("正在执行work2... %d" % i)
i += 1
# 切换到第一个任务
g1.switch()
if __name__ == '__main__':
g1 = greenlet(work1)
g2 = greenlet(work2)
# 执行work1
g1.switch()
看到的效果和上一个代码的效果完全一样。
3.3 gevent实现协程
????????greenlet已经实现了协程,但是这个还需要人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的第三方库gevent。
????????gevent的原理是当一个greenlet遇到IO(指的是input output输入输出﹐比如网络﹑文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
????????由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
import gevent
def work1():
i = 0
while True:
print("正在执行work1... %d" % i)
i += 1
gevent.sleep(2)
def work2():
i = 0
while True:
print("正在执行work2... %d" % i)
i += 1
gevent.sleep(0.5)
if __name__ == '__main__':
# 指派任务 gevent.spawn(函数名, 参数1, 参数2, ...)
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# 让主线程等待协程完毕后再退出
g1.join()
结果:
????????发现work1执行1次,work2执行4?次,这是由于执行work1耗费的时间是work2的4倍。(这里使用gevent.sleep()来模拟耗时操作,不用time.sleep()模拟的原因是gevent模块默认不把time.sleep()当做耗时操作)
????????如何让gevent模块把time.sleep()当做是一个耗时操作?
? ? ? ? 解决步骤:
? ? ? ? ? ? ? ? 1.导入monkey模块? ? ? ? ?from gevent import monkey
? ? ? ? ? ? ? ? 2.破解? ? ? ? ? ? ? ? ? ? ? ? ? ? ?monkey.patch_all()
具体如下:?
from gevent import monkey
monkey.patch_all()
import gevent,time
def work1():
i = 0
while True:
print("正在执行work1... %d" % i)
i += 1
time.sleep(2)
def work2():
i = 0
while True:
print("正在执行work2... %d" % i)
i += 1
time.sleep(0.5)
if __name__ == '__main__':
# 指派任务 gevent.spawn(函数名, 参数1, 参数2, ...)
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# 让主线程等待协程完毕后再退出
g1.join()
执行结果和上一个代码的执行结果完全一样。
4. 案例:协程实现并发下载器
python 并发下载器
|