个人笔记——多任务之协程

协程

迭代器

迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象,接待器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不能后退。
判断一个对象是否可以迭代:

from collections import Iterable  # 调用一个迭代类
isinstance([11, 22, 33], Iterable)  # 判断是否是迭代的子类
>>True

问题:如果自己创建一个带列表的类,是否能迭代呢?
例:

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

classmate = Classmate()

classmate.add("老王")
classmate.add("网二")
classmate.add("张三")
for name in classmate:
    print(name)
'''会提示错误,不可迭代'''

结论是不可以的,需要定义__iter__类
如下例:

from collections import Iterable

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

    def __iter__(self):
    	'''如果想让一个对象成为一个可以迭代的对象,即可以使用for
    	那么必须实现__iter__方法'''
        pass


classmate = Classmate()

classmate.add("老王")
classmate.add("网二")
classmate.add("张三")

print(isinstance(classmate, Iterable))
>>True
>但依旧不可以使用for--->print
>需要让__iter__返回一个对象的引用,对象引用的类内部必须要有__iter__方法和__next__方法

在使用for temp in xxxx_obj的过程中发生了什么呢?
1.判断xxxx_obj是否可以迭代(只要xxxx_obj内有__iter__函数即可)
2.在第1步成立的前提下,调用iter函数得到xxxx_obj对象的__iter__方法的返回值
3.__iter__方法的返回值是一个迭代器
下面我们创建一个自带__iter__和__next__方法的类,并且在原先创建的类的__iter__方法中return新创建的类:

from collections import Iterable
from collections import Iterator
import time


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

    def __iter__(self):
        return ClassIterator(self)


class ClassIterator(object):
    def __init__(self, obj):
        self.obj = obj
        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
            return ret
        else:
            raise StopIteration  # 不raise会一直打印None


classmate = Classmate()
classmate.add("老王")
classmate.add("网二")
classmate.add("张三")

# 判断是否可迭代
print(isinstance(classmate, Iterable))

classmate_iterator = iter(classmate)
# 判断是否是迭代器
print(isinstance(classmate_iterator, Iterator))

for name in classmate:
    print(name)
    time.sleep(1)

既然可以通过返回带有__iter__和__next__方法的类来使其可迭代,我们当然可以让原来本身的类也有__next__方法,然后再__iter__中返回self,这样在使用for的时候就会先判断我们这个类是否是可迭代的?即是否拥有__iter__方法?√
自动调用iter()方法,即会调用类中的__next__方法,返回对应的值
例:

class Classmate(object):
    def __init__(self):
        self.names = list()
        self.current_num = 0

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        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  # 不raise会一直打印None

classmate = Classmate()
classmate.add("老王")
classmate.add("网二")
classmate.add("张三")

for name in classmate:
    print(name)
    time.sleep(1)

迭代器的应用场景:用于调用存储的数据
问题:为什么我们不直接把数据存到一个列表里呢?因为直接放列表会占用内存,而迭代器可以随用随取,不会占用
例:斐波那契数列

class Fabonacci(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 = Fabonacci(10)

for num in fibo:
    print(num)
>>0
1
1
2
3
5
8
13
21
34

注:类型转换的实质不是单纯的转换,以a = (1, 3, 5, 7)元组为例,list(a)的实质是先生成一个空列表[ ],然后用元组a的迭代器依次取值,放入空列表,等到取完触发异常结束

生成器

生成器是一种特殊的迭代器,主要通过yield来迭代返回生成
例:

def creat_num(all_num):
    print("----1----")
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print("----2----")
        # print(a)
        yield a   
        '''如果一个函数中有yield语句,那么这个就不再是函数,而是一个生成器模版'''
        print("----3----")
        a, b = b, a+b
        current_num += 1
        print("----4----")

'''如果在调用create_num的时候,函数中有yield,
那么就不是调用函数,而是创建一个生成器对象
可以创建多个生成器你,例如obj2 = creat_num(3)'''
obj = creat_num(10)

ret = next(obj)
print(ret)

ret = next(obj)
print(ret)
>>----1----
----2----
0
----3----
----4----
----2----
1

注:如果生成器最后有return的值,则可以用生成器的对象ret调用value,即ret.value

生成器的send

obj.send()相当于传递了一个值v到生成器内部,使yield a的结果为传递进去的值v,同时send的结果会变成a的值,如果需要对后面的代码进行调控,就可以利用send传入进的值v
例:

def creat_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 = creat_num(10)

ret = next(obj)
print(ret)

ret = obj.send("hahahha")
print(ret)
>>0
----ret---- hahahha
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()

使用greeenlet实现多任务

相当于把yield的用法简化了,原先在True循环中调用next来达到切换进程,现在则是把这个控制步骤分别撞到各个函数当中,并且不再需要yield,而用greenlet自己封装的功能来达到切换效果
例:

from greenlet import greenlet
import time

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()

使用gevent实现多进程

gevent是一个网络的并发库,相当于对greenlet再次进行了封装
携程依赖于线程,线程依赖于进程
例:

import gevent
import time

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

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

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

g1 = gevent.spawn(f1, 5)  # 相当于创建了greenlet对象
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join()
g2.join()
g3.join()

如果想用gevent来实现多任务,则需要将程序内的延时操作代码都替换成gevent的方法,例如上例本来应该用的time.sleep,替换成了gevent.sleep。但这样的话会非常麻烦,这时候就需要打一个补丁:monkey.patch_all()
例:

import gevent
import time
from gevent import monkey

monkey.patch_all()


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


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


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


g1 = gevent.spawn(f1, 5)  # 相当于创建了greenlet对象
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
gevent.joinall([g1, g2, g3])

因为这种方式实际上是几个线程交替进行,所以不适用于聊天器之类的自由度比较高的设计

进程、线程、协程对比

1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源很大,效率很低
4.线程切换需要的资源一般,效率一般
5.协程切换任务资源很小,效率高
6.多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发

发布了33 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sinat_38354769/article/details/96460959