python协程学习

学习知识点:

  1.知识点叫什么

  2.知识点用在哪

  3.知识如何实现

 

一. 线程、进程、协程傻傻分不清楚

  1.进程 :启动多个进程 进程之间是由操作系统负责调用

     线程 :启动多个线程 真正被cpu执行的最小单位实际是线程

     协程:
          本质是是一个线程
          能够在多个任务之间切换来节省一些IO时间
          携程中任务之间的切换时间也消耗时间,但是时间开销要远远小于进程线程之间的切换

 

二. 协程最关键的点就是:gevent 

  1.gevent是什么?

  网络异步并发库,底层yield实现,对greenlet再次封闭

  2.作用:实现多任务,是效率最快,占用资源最少的一种方法

  3. 协程的 重点四步:

  

  4.使用gevent这个模块,必须要请猴子来打补丁,下面代码感受一下。

 1 import gevent
 2 import time
 3 
 4 
 5 def song():
 6     for i in range(5):
 7         print("唱歌")
 8         time.sleep(1)
 9 
10 
11 def dance():
12     for i in range(5):
13         print("跳舞")
14         time.sleep(1)
15 
16 
17 def main():
18     """一边唱歌一边跳舞"""
19     # 加入协程
20     song_g = gevent.spawn(song)
21     dance_g = gevent.spawn(dance)
22 
23     # 让我们的主线程等待协程的任务做完
24     song_g.join()
25     dance_g.join()
26 
27 
28 if __name__ == '__main__':
29     main()
不请猴子打补丁的代码
打印结果:time休眠并没起到作用
唱歌
唱歌
唱歌
唱歌
唱歌
跳舞
跳舞
跳舞
跳舞
跳舞 
 1 import gevent
 2 import time
 3 from gevent import monkey  # 请猴子打补丁
 4 
 5 # 用到协程就请猴子 打补丁
 6 monkey.patch_all()
 7 
 8 
 9 def song():
10     for i in range(5):
11         print("唱歌")
12         time.sleep(1)
13 
14 
15 def dance():
16     for i in range(5):
17         print("跳舞")
18         time.sleep(1)
19 
20 
21 def main():
22     """一边唱歌一边跳舞"""
23     # 加入协程
24     song_g = gevent.spawn(song)
25     dance_g = gevent.spawn(dance)
26 
27     # 让我们的主线程等待协程的任务做完
28     song_g.join()
29     dance_g.join()
30 
31 
32 if __name__ == '__main__':
33     main()
打过补丁的代码
打印结果:
唱歌
跳舞
唱歌
跳舞
唱歌
跳舞
唱歌
跳舞
唱歌
跳舞

  注意点:

    1.其实也可以不用请猴子打补丁,只需要把time.sleep() 改成 gevent.sleep() 就可以了,不过这样很麻烦,一般请个猴子打补丁之后,代码该咋写就咋写。下面两张图,感受一下:

    

    2.gevent碰到延时的自动切换,所有的耗时的都需要gevent库中的耗时方法进行更换太麻烦。

    

  5. 其实上面的代码也是有缺陷的,join() 只能等待一个协程,如果很多怎么办?这时候就需要joinall().

  joinall与join的区别: 

    joinall可以用在单任务等待,也可以用在批量任务等待
    join只能用在单任务等待
    joinall是把列表中的所有等待的时间都执行了在结束,
    join只能等待一个

 1 # 1. 导入gevent
 2 # 2. 请求猴子打补丁
 3 # 3. 加入任务
 4 # 4. 加入等待列表中
 5 
 6 
 7 import gevent
 8 import time
 9 
10 from  gevent import monkey
11 
12 # 打补丁
13 monkey.patch_all()
14 
15 
16 # 定义任务
17 def song():
18     while True:
19         print("唱歌")
20         time.sleep(1)  # 打了补丁以后,在执行代码时会把当前的耗时代码换成gevent的耗时代码
21     # gevent.sleep(1) # 必须使用gevent中的耗时操作
22 
23 
24 def dance():
25     for temp in range(5):
26         print("跳舞")
27         time.sleep(1)
28 
29 
30 def main():
31     """使用协程去执行多任务"""
32     # 加入到任务中
33     song_g = gevent.spawn(song)
34     dance_g = gevent.spawn(dance)
35 
36     # 加入到等待列表中
37     gevent_list = list()  # 空的列表
38     # dict() 空的字典
39     # 加入到列表中
40     gevent_list.append(song_g)
41     gevent_list.append(dance_g)
42 
43     # 让我们的主线程等待
44     gevent.joinall(gevent_list)  # 等待的名单
45 
46 
47 # 结束
48 
49 
50 if __name__ == '__main__':
51     main()
joinall()的使用
打印结果:
唱歌
跳舞
唱歌
跳舞
唱歌
跳舞
唱歌
跳舞
唱歌
跳舞
唱歌
唱歌
唱歌
唱歌
唱歌
唱歌

  重点:以后协程使用就按照上面四步来就可以了。来张图增加记忆。

  

  6.协程的传参 

song_g = gevent.spawn(song, "python19", 70)  # 第一个函数的引用,第二个以后都是参数值

  

二. 迭代器

  1.先对迭代器来个概念:

  Python中iterable被认为是一个对象,这个对象可以一次返回它的一个成员(也就是对象里面的元素),由此可知,Python中的string,list,tuple,dict,file,xrange都是可迭代的,都属于iterable对象,

  可迭代的对象都是可以遍历的,实际上Python中有很多iterable类型是使用iter()函数来生成的。

  2.什么是迭代器: 访问集合元素的一种方式

  3.迭代器的作用: 可以通过for循环来获取实现迭代的类中存的数据。在数据有规则的情况下,迭代器毫无疑问是内存使用最小的,所以迭代器一般用于科学计算。

      注意点:没有规则(无序的,无公式的)无法使用迭代器。

  4.如何判断是不是迭代器。

  

  5. 自定义迭代器,需要两个魔法方法,来个图

  

 1 # 迭代器就是一个可以被for遍历的对象
 2 # 实现两个方法__iter__和__next__
 3 # 如果只实现__iter__这个不是迭代器只是一个可迭代的对象
 4 
 5 
 6 class MyIter(object):
 7 
 8     def __init__(self):
 9         self.num = 0  # 初始化的值
10 
11     # 这个iter必须返回迭代器对象
12     def __iter__(self):
13         return self  # 在自定义迭代器时,一般都是返回self
14 
15     def __next__(self):
16         # 停止迭代
17         self.num += 1
18         # 如果大于50停止
19         if self.num > 5:
20             # 这个有点意思,当停止迭代时,是报错的,所以需要手动抛出异常
21             raise StopIteration
22         return self.num
23 
24 for temp in MyIter():
25     print(temp)  # 这个打印出来是来是1-5 这也很有意思
26 
27 # 做个试验
28 a = list()  # 系统自带的列表,打印出来就是0-4,至今搞不清楚
29 for temp in range(5):
30     a.append(temp)
31 for temp in a:
32     print(temp)  # 也是5个数据,不过是0-4
自定义迭代器
打印结果:
1
2
3
4
5
0
1
2
3
4

  6.自定义迭代器与已有集合区别

    1.迭代器存的是生成的公式,列表存的是具体的值

    2.迭代器占用空间会小很多,来个案例看下内存的占用

    3.迭代器必须是有规律的可以用公式才能体现省内存

    4.迭代器使用内存比我们的列表要小,前提是数据必须有规则,如果没有规则 ,那么一般不能使用迭代器

      前置知识查看内存:http://blog.csdn.net/xiaodongxiexie/article/details/54633049

    

 1 # 迭代器就是一个可以被for遍历的对象
 2 # 实现两个方法__iter__和__next___
 3 # 如果只实现了 __iter___这个不是迭代器只是一个可迭代的对象
 4 
 5 # 迭代器使用内存比我们的列表要小,前提是数据必须有规则,如果没有规则 ,那么一般不能使用迭代器
 6 # 迭代器用在科学计算
 7 
 8 class MyIter(object):
 9     def __init__(self):
10         self.num = 0  # 初始化的值
11 
12     # 这个iter必须返回迭代器对象
13     def __iter__(self):
14         # print("iter")
15         return self  # 在自定义迭代器时,一般都是返回self
16 
17     # 返回迭代时的值
18     def __next__(self):
19         # print("next")
20         # 停止迭代
21         self.num += 2
22         # 判断如果大于50停止
23         if self.num > 1000000:
24             # 停止
25             # 手动抛出异常
26             raise StopIteration  # 停止迭代
27 
28         return self.num
29 
30 
31 # 得到迭代器对象
32 my_iter = MyIter()
33 
34 for temp in my_iter:
35     print(temp)
36 
37 # 常用的:
38 import psutil
39 import os
40 
41 # 查看内存
42 info = psutil.virtual_memory()
43 print(u'内存使用:', psutil.Process(os.getpid()).memory_info().rss)
44 print(u'总内存:', info.total)
45 print(u'内存占比:', info.percent)
46 print(u'cpu个数:', psutil.cpu_count())
自定义迭代器演示

  7.xrange与range的区别

  xrange在python2中存在,不要存实际的数据,存的是公式省内存

  

  python3与python2的range

  

  8.iter与next方法使用,理解:迭代器就好比你父亲原先是一次性给你一百万,现在是你需要多少给你多少,给完就结束

  

  9.for的本质,我快看晕了,先放这吧。

 1 # 来探究一下for的本质
 2 
 3 class MyIter(object):
 4 
 5     def __init__(self):
 6         self.num = 0
 7 
 8     def __iter__(self):
 9         return self
10 
11     def __next__(self):
12         self.num += 2
13         if self.num > 10:
14             raise StopIteration
15         return self.num
16 
17 # 下面的代码才是本质
18 # iter方法得到迭代器
19 my_iter = MyIter()
20 # 得到迭代器  这个iter是个内建函数
21 iter_my = iter(my_iter)  # 调用__iter__方法
22 # 迭代一次
23 print(next(iter(my_iter)))  # 调用__next__方法
24 print(next(iter(my_iter)))  # 调用__next__方法
25 print(next(iter(my_iter)))  # 调用__next__方法
26 print(next(iter(my_iter)))  # 调用__next__方法
27 print(next(iter(my_iter)))  # 调用__next__方法
for的本质
打印结果:
2
4
6
8
10

三. 生成器

  1.什么是生成器: 一种特殊的迭代器

  2.作用: 跟迭代器作用一致

  3.如何写一个生成器?2种方法

    1.列表推导式生成元组的方式就是生成器对象, 把[] 改成 () 就是生成器了

    

    2.使用yield关键字

 1 # 只要你的函数有了yield关键字,那么就是生成器了,就是for
 2 def my_iter():
 3     for temp in range(10):
 4         yield temp  # 这个是暂停
 5 
 6 
 7 print(my_iter())
 8 
 9 for temp in my_iter():
10     print(temp)
生成器代码
打印结果:
<generator object my_iter at 0x0000017B4C07B518>
0
1
2
3
4
5

  4.给生成器执行过程中传值  send的使用

 1 # send 跟next作用是一样,但是send可以传参
 2 
 3 def My_iter():
 4     for temp in range(10):
 5         print(temp)
 6         # 接收值
 7         value = yield temp
 8         print(value)
 9 
10         print("打印")
11 
12 # 得到迭代器对象
13 iter_my = iter(My_iter())
14 # 迭代
15 next(iter_my)  # 这个只打印temp 碰到yield就停止不打印了 
16 # send 只能在这之后使用,不然报错
17 next(iter_my)  # 这个从yield处接着向下执行,知道再次碰到yield
18 next(iter_my)
19 iter_my.send('haha')  # 在暂停启动后可以传参数,了解一下,只能第二次使用
send()的使用
打印结果:
0
None
打印
1
None
打印
2
haha
打印
3

  5.小结

    1.迭代第一次全部执行到yeild结束,返回结果,第二次以后的执行都是在yeild后面的代码执行

    2.普通的方法只要写了yield就是生成器

    3.yield有点像我们的input()函数,只有用户输入值才会返回,yield需要用户调用next()或者 for才执行

    4.通过next()这个函数进行返回值,每次只返回一个值,是从0下标开始,直到元素取完,结束(报错)

    5.generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到最后一个元素,没有更多元素时,抛出StopIteration的错误

  6.使用yield完成多任务

    1. 作用:可以利用耗时操作的空闲时间提高效率

    2.与进程,线程对比:协程切换任务资源最少,就像调用函数一样简单

    

四. greenlet   了解

  1.是什么:对yield进封装

  2.作用:实现多任务

  3.代码实现

 1 import greenlet
 2 import time
 3 # 使用greenlet 需要手动把所有函数连接起来,形成一个闭环的效果
 4 
 5 
 6 def song():
 7     while True:
 8         print("唱歌")
 9         time.sleep(1)
10         # 切换到跳舞
11         dance_g.switch()
12 
13 
14 def dance():
15     while True:
16         print("跳舞")
17         time.sleep(1)
18         # 切换到唱歌
19         song_g.switch()
20 
21 # 加入两个任务
22 song_g = greenlet.greenlet(song)
23 dance_g = greenlet.greenlet(dance)
24 
25 # 开启  都可以开启,只不过是谁先的问题
26 song_g.switch()
27 dance_g.switch()
greenlet的代码
打印结果:
唱歌
跳舞
唱歌
跳舞

  4.来个案例,从网上下载图片

 1 # 创建一个批量的图片的列表
 2 # 循环的去下载 图片
 3 
 4 # 协程根据耗时进行自动切换
 5 
 6 
 7 from urllib.request import *
 8 
 9 import gevent
10 
11 from  gevent import monkey
12 
13 # 打补丁
14 monkey.patch_all()
15 
16 
17 def down_image(down_path, save_path):
18     """
19     下载图片
20     :param down_path: 下载的地址
21     :param save_path: 保存的地址
22     :return: NONE
23     """
24 
25     print("下载图片开始:", save_path)
26 
27     # 打开下载的图片,得到内容
28     image = urlopen(down_path)
29     # 得到内容
30     content = image.read()
31 
32     # 保存
33     with open(save_path, 'wb') as f:
34         f.write(content)
35 
36     print("下载图片结束:", save_path)
37 
38 
39 def main():
40     """批量下载图片"""
41     # 创建一个批量图片的列表
42     image_list = list()
43     image_list.append("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3506739232,2945471821&fm=27&gp=0.jpg")
44     image_list.append("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1293860636,1088191402&fm=27&gp=0.jpg")
45     image_list.append("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3711458043,3147749033&fm=27&gp=0.jpg")
46 
47     # 图片的位置
48     num = 1
49 
50     # 创建一个协程等待的列表
51     gevent_list = list()
52 
53     # 循环去下载
54     for temp in image_list:
55         # 下载
56         # down_image(下载的路径,保存的路径)
57         save_path = "./images/%s.jpg" % num
58         # 把耗时的任务放到多任务中
59         # 把任务 加入到协程中
60         g_down_image = gevent.spawn(down_image, temp, save_path)
61 
62         # 加入到等待的列表中
63         gevent_list.append(g_down_image)
64 
65         # down_image(temp, save_path)
66         # 图片的顺序加1
67         num += 1
68 
69     # 让主线程等待
70     gevent.joinall(gevent_list)
71 
72 
73 if __name__ == '__main__':
74     main()
View Code

 

五. 总结

  1. 进程、线程、协程之间的关系

  

  2.进程、线程、线程的对比

  

  

  

  

  

  

 

 

 

转载于:https://www.cnblogs.com/grave-seven/p/9574242.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值