多任务之协程

多任务之协程


迭代器

  • 迭代:是访问集合元素的一种方式,用 for....in...list、tuple、str等类型中依次取数据,这种遍历也称之为迭代
  • 迭代对象:可以通过 for....in...这类语句迭代读取数据的对象
  • 迭代器:是一个可以记住遍历的位置的对象。
实现一个可迭代对象
from collections import Iterable
import time

class Classmate(object):
    def __init__(self):
        self.names = list()
        self.i = 0
    
    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """把对象变成可以迭代 即可以用for """
        return self

    def __next__(self):
        # 方法内部定义变量,下次调用不会保留上次的值,需定义在__init__内
        # i = 0
        if self.i < len(self.names):
            ret = self.names[self.i]
            self.i += 1
            return ret
        else:
            raise StopIteration   # 自定义一个异常,告诉for已经遍历完毕了

classmate = Classmate()

classmate.add("老王")
classmate.add("张三")
classmate.add("李四")

print("判断classmate是否可以迭代的对象:", isinstance(classmate, Iterable))

iter(classmate)

for name in classmate:
    print(name)
    time.sleep(1)
  • 迭代器最核心的功能是储存生成数据的方式代码,而不是生成数据的本身,即不需要一个空间来存储数据,可节省大量资源空间
斐波拉契数列(Fibonacci)

数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

nums = list()
a, b, i = 0, 1, 0
while i < 10:
    nums.append(a)
    a, b = b, a + b
    i += 1
for num in nums:
    print(num)
用迭代器实现Fibonacci
class Fibonacci(object):
	
    def __init__(self, all_num):
        self.all_num = all_num
        self.i = 0  # 用来保存当前生成到数列中的第几个数了
        self.a = 0  # 用来保存前前一个数,初始值为数列中的第一个数0
        self.b = 1  # 用来保存前一个数,初始值为数列中的第二个数1

    def __iter__(self):
        """迭代器的__iter__返回自身即可"""
         return self

    def __next__(self):
      """被next()函数调用来获取下一个数"""
        if self.i < self.all_num:
            ret = self.a

            self.a, self.b = self.b, self.a + self.b
            self.i += 1
            return ret
        else:
            raise StopIteration

fibo = Fibonacci(10)
for num in fibo:
    print(num)

生成器

  • 生成器是一类特殊的迭代器
生成器实现Fibonacci
def create_num(all_num):
    a, b = 0, 1
    i = 0
    while i < all_num:
        # print(a)
        # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器
        yield a
        a, b = b, a + b
        i += 1

# 这时调用,不是在调用函数,而是创建一个生成器对象
# create_num(10)
obj = create_num(10)

for x in obj:
    print(x)
  • 简单来说:只要在def中有yield关键字的 就称为 生成器,不在是函数
  • yield关键字有两点作用:
    • 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
    • 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
  • 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
用异常判断生成器结束
def create_num(all_num):
    a, b = 0, 1
    i = 0
    while i < all_num:
        # print(a)
        # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器
        yield a
        a, b = b, a + b
        i += 1
    return "完成..."

obj = create_num(10)  # 这时调用,不是在调用函数,而是创建一个生成器对象

while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        print(ret.value)
        break

用send启动生成器
  • 可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据
def create_num(all_num):
    a, b = 0, 1
    i = 0
    while i < all_num:
        ret = yield a
        print(">>>>ret>>>>", ret)
        a, b = b, a + b
        i += 1

obj = create_num(10)
ret = next(obj)
print(ret)

# send里面的数据会传递给第5行,当做yield a的结果,然后ret保存这个结果
# send的结果会是下一次调用yield时 yield后面的值
ret = obj.send("hhhh")  # send一般不会的放到第一次启动,除非传递的是None
print(ret)

协程

  • 协程是python个中另外一种实现多任务的方式
  • 通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定
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()
    # 先让t1运行一会,当t1中遇到yield的时候,再返回到 while True
    # 执行t2,当它遇到yield的时候,再次切换到t1中
    # 相当于t1/t2交替执行,这就是协程完成多任务
    while True:
        next(t1)
        next(t2)

if __name__ == '__main__':
    main()
greenlet
  • 为了更简单的切换任务,python中的greenlet模块对其封装
  • greenlet可能需要单独安装(有些默认没有装)
from greenlet import greenlet
import time

def test_1():
    while True:
        print("----1----")
        gr2.switch()
        time.sleep(0.5)  # 延时为了更好的看效果

def test_2():
    while True:
        print("----2----")
        gr1.switch()
        time.sleep(0.5)

gr1 = greenlet(test_1)
gr2 = greenlet(test_2)

gr1.switch()  # 切换到gr1中运行
gevent(最常用!!这个是真重点!!!)
  • 虽然greenlet已经实现了协程,但还还需去单独去切换
  • gevent模块就比之前的两个都好用,而且可以自动切换
import gevent
import time
from gevent import monkey

monkey.patch_all()  # 打补丁,将下面的延时操作变成 gevent.sleep(0.5)

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        #用来模拟一个耗时操作,注意不是time模块中的sleep
        time.sleep(0.5)  # 若没有上面的补丁,则这句话没效果,就是安顺序执行,不是多任务
		 #遇到延时等待,切换到下一个执行 

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

gevent.joinall([
    gevent.spawn(f1, 5),
    gevent.spawn(f2, 5),
    gevent.spawn(f3, 5)
])
# g1.join()
# g2.join()
# g3.join()

gevent任务切换是需要堵塞,如,当执行到 f1 里的 time.sleep(0.5) 时, gevent就发现遇到了延时,程序处于等待状态,这时, gevent 就会切换到 f2 中执行,同理,就实现了多任务(并发)


案例-图片的爬取(gevent)

import urllib.request
import gevent
from gevent import monkey

monkey.patch_all()  # 有耗时操作时就需要,反正加上也没毛病

def download(img_name, img_url):
    req = urllib.request.urlopen(img_url)

    img_content = req.read()

    with open(img_name, "wb") as f:
        f.write(img_content)

def main():
        gevent.joinall([
           gevent.spawn(download, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2019/05/05/6463290_20190505155227_small.jpg"),
           gevent.spawn(download, "2.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2019/04/03/6532591_20190403195850_small.jpg"),
           gevent.spawn(download, "3.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2019/04/24/6496091_20190424210517_small.jpg"),
   ])

if __name__ == '__main__':
    main()
  • 试验试验而已,爬了三张妹子。。。。。把地址换了也可以是下载视频,音乐啥的。。

END…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值