什么是进程?
程序在没运行起来之前是死的,程序运行起来后就是进程,进程跟程序之间的区别就是进程拥有资源,例如登陆QQ之后,QQ可以调用声卡、摄像头等。
两个进程之间的通信,利用队列Queue,放到内存里,一个写,一个读。缺点就是队列只能在同一个程序或者电脑中运行,要在多台电脑之间进行,用到缓存redis。
import multiprocessing def download_data(q): # 下载数据 data = [11, 22, 33, 44] for i in data: q.put(i) def analysis_data(q): while True: data = q.get() print(data) if q.empty(): break def main(): q = multiprocessing.Queue(4) p1 = multiprocessing.Process(target=download_data, args=(q, )) p2 = multiprocessing.Process(target=analysis_data, args=(q, )) p1.start() p2.start() if __name__ == "__main__": main()
进程池
进程池里面有预先指定好的进程数,当很多任务一起过来时,如果进程被调用完,其他任务就一直等着,等待完成任务的进程来调用它们。好处就是减少了进程的创建和销毁所花的时间和资源。
协程
1、迭代器
在原来的基础上去得到一个新的东西,这就是迭代。
# 判断是否可迭代 >>> from collections import Iterable >>> isinstance([11, 22, 33], Iterable) True
from collections import Iterable class Classmatename(): def __init__(self): self.names = list() def add(self, name): self.names.append(name) def __iter__(self): """如果想要一个对象称为一个 可以迭代的对象,既可以使用for,那么必须实现__iter__方法""" pass classmatename = Classmatename() classmatename.add("张三") classmatename.add("李四") classmatename.add("王五") print(isinstance(classmatename, Iterable)) # ------》 True
但如何循环打印列表里面的值呢?
首先先想一下当我们循环打印列表的时候发生了什么,每次循环,就会打印一个值,下一次循环打印下一个值,由此我们应该可以猜想有一个东西,在记录着我们打印到哪了,这样才可以在下一次打印接下来的值。对于我们上面创建的类,现在它已经是可以迭代了,但是还没有一个东西来记录它应该打印什么,打印到了哪里。
for item in xxx_obj: pass 1、判断xxx_obj是否是可以迭代; 2、在第一步成立的前提下,调用iter函数,得到xxx_obj对象的__iter__方法的返回值; 3、__iter__方法的返回值是一个 迭代器
什么是迭代器?一个对象里面有“__iter__”方法,我们称之为 可以迭代。如果"__iter__"方法返回的对象里面既有“__iter__”又有"__next__"方法,那么我们称这个对象为 迭代器。
所以为了可以打印,这里需要两个条件:1、有iter值;2、iter值返回一个对象引用。这个对象里面除了iter方法外还有一个next方法。
因此大体过程就是for循环调用,首先判断这个对象是不是可迭代的(有"__iter__",是),接下来自动用iter方法调用"__iter__",获取返回的“对象引用”。接下来for循环调用这个对象里面的“__next__”方法,调一次,next方法返回什么,就输出什么给item打印。
from collections import Iterator classmate_Iterator = iter(classmatename) # 用iter获取迭代器 # 判断是否是迭代器 print(isinstance(classmate_Iterator, Iterator))
import time class Classmatename(): def __init__(self): self.names = list() self.current_num = 0 def add(self, name): self.names.append(name) def __iter__(self): """如果想要一个对象称为一个 可以迭代的对象,既可以使用for,那么必须实现__iter__方法""" # 创建实例对象 return ClassIterator(self) # self指向这个类本身,然后传给类ClassIterator
class ClassIterator(): def __init__(self, obj): self.obj = obj # self.obj指向实例对象Classmatename self.current_num = 0 def __iter__(self): pass def __next__(self): if self.current_num < len(self.obj.names): ret = self.obj.names[self.current_num] self.current_num += 1 # 注意这里的self.current_num=0要放在__init__下,如果current_num=0放在__next__下,则for循环每次调用__next__时,current_num都会被重新记零 return ret else: raise StopIteration # 抛出异常,告诉for循环可以结束了
classmatename = Classmatename() classmatename.add("张三") classmatename.add("李四") classmatename.add("王五") for item in classmatename: print(item) time.sleep(1)
另一种办法,写在一起
import time class Classmatename(): 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 # 返回自身给for循环调用里面的__next__ 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 # 抛出异常,告诉for循环可以结束了 classmatename = Classmatename() classmatename.add("张三") classmatename.add("李四") classmatename.add("王五") for item in classmatename: print(item) time.sleep(1)
迭代器的优点
优先我们先来了解下Python2中的range和xrange区别
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> xrange(10)
xrange(10)
由上图可知,range和xrange的区别就是range会直接返回生成的结果,而xrange返回的是生成这个结果的方式,什么时候需要调用里面的值,它才会去生成,所以xrange占用的内存空间要小很多。(这个问题在Python3已经解决了,Python3的range相当于Python2里面的xrange)
迭代器也是一样,生成的是调用值的方式,什么时候要调用值,才去生成,因此占用很少的内存空间。
# 用迭代器的方法生成斐波那契数列 class Fibonacci(object): def __init__(self, all_num): self.all_num = all_num self.current_num = 0 self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): if self.current_num < self.all_num: ret = self.a self.a, self.b = self.b, self.a+self.b self.current_num += 1 return ret else: raise StopIteration fibo = Fibonacci(10) for num in fibo: print(num)
a = (11, 22, 33) list(a) # 首先生成一个空列表,然后循环调用a里面的迭代器,把值一个个放到列表中去 [11, 22, 33]
生成器(是一种特殊的迭代器)
生成生成器的第一种方式
>>> nums1 = [x*2 for x in range(10)] >>> nums1 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> nums2 = (x*2 for x in range(10)) >>> nums2 <generator object <genexpr> at 0x00AF5180>
其中,num2就是生成器。可以通过for循环遍历里面的值。num1和num2的区别就是前面说的,num2不占用空间,值在需要时才会被生成。
生成生成器的第二种方式
# 用普通函数生成斐波那契数列 def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: print(a) a, b = b, a+b current_num += 1 create_num(10)
用生成器的方法生成斐波那契数列,添加yield
# 生成器 def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: yield a # 如果一个函数中有yield语句,那么这个就不再是函数,而是一个生成器的模板
a, b = b, a+b current_num += 1 # 如果在调用create_num的时候,发现这个函数有yield,那么不在是调用函数,而是创建一个生成器对象 obj = create_num(10)
for num in obj: # for循环调用过程。首先创建obj,当开始执行for循环时,程序开始向下走,
print(num) # 当到达yield a的时候停止,把a的值传给num,打印出来。然后for继续调用,于是从停止的位置也就是yield那里继续向下执行,而不会从函数的开头重新运行了。
用 "ret = next(obj)" 可以一次调用一个值用来验证下。
def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: yield a a, b = b, a+b current_num += 1 return ".....ok....." obj = create_num(10) while True: try: ret = next(obj) print(ret) except Exception as ret: print(ret.value) # 这个value就是return返回的值 break
第二中调用生成器的方法:send() (一般不用做第一次启动,如果非要,send()里面只能传递None)
def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: ret = yield a print(">>>>>>>>ret>>>>>>>", ret) a, b = b, a+b current_num += 1 obj = create_num(10) ret = next(obj) print(ret) ret = obj.send("hahaha") # 首先程序运行到"yield a"时停止,把“a=0”传给ret打印出来。接下来运行"send("hahaha")”,从“ret=yield a“开始,此时"yield a"并没有返回值给等号左边的ret,那么就把"hahaha" print(ret) # 传给ret,由"print(">>>>>>>>>ret>>>>>>>>>", ret)"打印出来。再继续执行接下来的步骤。
# 结果返回 0 >>ret>>>>>>> hahaha 1
总结:迭代器特点是占用内存空间小,什么时候用,什么时候生成;
生成器有迭代器的特点,而且它最大的特点就是可以执行到一半时暂停,返回结果,然后再继续在原来基础上继续执行。
用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()
协程(单进程单线程)最大的意义,把原本等待的时间利用起来去做别的事情。
协程依赖于线程,线程依赖于进程。
有时程序写了很多行了,用的是time.sleep(),这时不想用gevent里面的gevent.sleep()方法,可以给程序打补丁
小案例,用gevent实现图片下载
import urllib.request import gevent from gevent import monkey monkey.patch_all() def download_img(file_name, url): img = urllib.request.urlopen(url) img_content = img.read() with open(file_name, "wb") as f: # 这里没有用time模块是因为网络下载过程中本来就会延时,相当于我们前面实例的time.sleep() f.write(img_content) def main(): gevent.joinall([ gevent.spawn(download_img, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/08/31/3279944_20180831104533_small.jpg"), gevent.spawn(download_img, "2.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/11/14/910907_20181114154402_small.jpg") ]) if __name__ == "__main__": main()