迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有元素被访问完结束。迭代器只能往前不会后退。
判断是否可迭代
In [1]: from collections.abc import Iterable
In [2]: isinstance("abc",Iterable)
Out[2]: True
In [3]: isinstance([1,2,3],Iterable)
Out[3]: True
In [4]: isinstance(123,Iterable)
Out[4]: False
返回为True即为可迭代
迭代器
class Classmate(object):
def __init__(self):
self.names = list()
self.current_num = 0
def add(self,name):
self.names.append(name)
def __iter__(self):
#如果想要一个对象 可以迭代,即可以使用for,那么必须实现__iter__方法
return self
def __next__(self):
if self.current_num < len(self.names):
ret = self.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration
classmate = Classmate()
classmate.add("小1")
classmate.add("小2")
classmate.add("小3")
for i in classmate:
print(i)
迭代器的应用(斐波那契数列)
class Fibonacci(object):
def __init__(self,max_num):
self.max_num = max_num
self.current_num = 0
self.front = 0
self.after = 1
def __iter__(self):
return self
def __next__(self):
if self.current_num < self.max_num:
ret = self.front
self.front, self.after = self.after, (self.front + self.after)
self.current_num += 1
return ret
else:
raise StopIteration
fibo = Fibonacci(20)
for num in fibo:
print(num)
迭代器的其他使用方法
并不是只有for循环能够接受可迭代对象,除for循环外,list、tuple等也能接受。
a = (11,22,33)
list(a)
#此处list(a)不是单纯的类型转换,而是首先创建一个空列表,然后调用next方法,一个一个的往列表中添加。
生成器
生成器是一种特殊的迭代器
创建生成器
方法1
In [1]: nums = [x*2 for x in range(5)]
In [2]: nums
Out[2]: [0, 2, 4, 6, 8]
In [3]: nums = (x*2 for x in range(5))
In [4]: nums
Out[4]: <generator object <genexpr> at 0x000001DC00D7AF20>
方法2
如果一个函数中,存在yield语句,那么它就不是一个函数而是一个生成器
def create_num(all_num):
a, b, current_num = 0, 1, 0
while current_num < all_num:
yield a
a, b = b, a + b
current_num += 1
generator = create_num(100)
#generator就是一个生成器对象
for num in generator:
print(num)
生成器也是一个特殊的迭代器(使用next函数访问)
def create_num(all_num):
a, b, current_num = 0, 1, 0
while current_num < all_num:
yield a
a, b = b, a + b
current_num += 1
return "ok ..."
generator = create_num(100)
for i in range(102):
try:
print(next(generator))
except StopIteration as e:
print(e.value)
break
send唤醒
send与next的区别,send可以向生成器中传参
def create_num(all_num):
a, b, current_num = 0, 1, 0
while current_num < all_num:
ret = yield a
print(ret)
a, b = b, a + b
current_num += 1
return "ok ..."
generator = create_num(100)
print(next(generator))
print(generator.send("传参"))
第一次启动生成器,如果使用send,不能传值。第一次建议使用next,非要使用send,可以传入None这个空值。
使用yield完成多任务
import time
def task_1():
while True:
print("---1---")
time.sleep(0.1)
yield
def task_2():
while True:
print("---2---")
time.sleep(0.1)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
使用greenlet完成多任务
安装greenlet
pip install greenlet
greenlet
import time
from greenlet import greenlet
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)
gr1.switch()
greenlet已经实现了协程,但是还需要人工切换。gevent可以自动切换
使用gevent完成多任务
安装gevent
pip install gevent
简单实现
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
#只有使用gevent.sleep才会切换
gevent.sleep(1)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()
给程序打补丁
monkey.patch_all()
import gevent
from gevent import monkey
import time
monkey.patch_all()
#monkey.patch_all()给程序打补丁,程序当遇到耗时的代码,会换为gevent中自己实现的模块
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
#只有使用gevent.sleep才会切换
time.sleep(1)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()
简洁写法
import gevent
from gevent import monkey
import time
monkey.patch_all()
#monkey.patch_all()给程序打补丁,程序当遇到耗时的代码,会换为gevent中自己实现的模块
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
#只有使用gevent.sleep才会切换
time.sleep(1)
gevent.joinall([gevent.spawn(f,5),gevent.spawn(f,5),gevent.spawn(f,5)])
案例-图片下载器(利用协程提高速度)
import gevent
from gevent import monkey
import urllib.request
monkey.patch_all()
#monkey.patch_all()给程序打补丁,程序当遇到耗时的代码,会换为gevent中自己实现的模块
def download_img(url):
req = urllib.request.urlopen(url)
with open(url[-8:],'wb') as file:
file.write(req.read())
def main():
gevent.joinall([
gevent.spawn(download_img,"https://assets.ubuntu.com/v1/3887354e-CVE-Priority-icon-High.svg"),
gevent.spawn(download_img,"https://www.venustech.com.cn/u/cms/www/202106/11174004guxu.png"),
gevent.spawn(download_img,"https://www.baidu.com/img/flexible/logo/pc/result.png")])
if __name__ == "__main__":
main()
进程、线程、协程对比
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很大,效率很低
- 线程切换需要的资源一般,效率一般(不考虑GIL情况下)
- 协程切换任务资源很小,效率高
- 多进程、多线程根据CPU核数不一样可能是并行,但是协程是在一个线程中 所以是并发