多任务之协程

  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kun1280437633/article/details/80325984

1. 可迭代对象

我们已经知道可以对list、tuple、str等类型的数据使用for...in...的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

我们把可以通过for...in...这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)**。

可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

demo:

from collections import Iterable
isinstance([], Iterable)
 True

2. 自定义可迭代对象

代码

from collections import Iterable

class PythonClass(object):

    def __init__(self, name):
        self.name = name 
# 班级的名字
        self.students = []  # 班级里的学生信息

    def add_stu(self, stu):
        """添加一个学生"""
        self.students.append(stu)

    def __iter__(self):
        """
        只要在类里提供__iter__方法,那么这个类创建出来的就是可迭代对象
        __iter__ 必须要返回一个迭代器
        """

        return iter(self.students)

class= PythonClass('奥数班')

class.add_stu('吴彦祖')
class.add_stu('古天乐')
class.add_stu('渣渣辉')

print('class是否是可迭代对象:', isinstance(class, Iterable))

for stu in class:

    print('学生有:', stu)

结果:

class是否是可迭代对象: True
学生有: 吴彦祖
学生有: 古天乐
学生有: 渣渣辉

3. 可迭代器

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

list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。iter()函数实际上就是调用了可迭代对象的__iter__方法。

demo:

>>> li = [11, 22]
>>> li_iter = iter(li)
>>> next(li_iter)
11
>>> next(li_iter)
22
>>> next(li_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
注意:当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常,来告诉我们所有数据都已迭代完成,不用再执行next()函数了。
可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

demo :

In [56]: from collections import Iterator

In [57]: isinstance([], Iterator)
Out[57]: False

可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

demo:

In [56]: from collections import Iterator

In [57]: isinstance([], Iterator)
Out[57]: False

通过上面的分析,我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法(Python3中是对象的__next__方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的__next__方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。

一个实现了__iter__方法和__next__方法的对象,就是迭代器。

4. 自定义可迭代器

demo
from collections import Iterable
from collections import Iterator

class PythonClass(object):
    def __init__(self, name):
        self.name = name  # 班级的名字

        self.students = []  # 班级里的学生信息

    def add_stu(self, stu):
        """添加一个学生"""

        self.students.append(stu)

    def __iter__(self):
        mit = MyIterator(self.students)  # 可迭代对象的 __iter__ 方法必须返回一个迭代器对象
        print('mit 是否是迭代器:', isinstance(mit, Iterator))
        return mit

class MyIterator(object):
    """
    迭代器必须要有 __iter__ 和 __next__ 方法
    """
    def __init__(self, students):
        self.students = students  # 迭代器需要临时保存学生信息

        self.current_index = 0  # 迭代器要记录当前正在遍历的位置

    def __iter__(self):

        pass

    def __next__(self):
        """__next__ 必须要有返回值,供 for 循环使用"""
        if self.current_index < len(self.students):
            stu = self.students[self.current_index]
            self.current_index += 1
            return stu
        else:
            # 没有数据了

            raise StopIteration()

class= PythonClass('奥数班')

# print('班级的名字是:', class.name)

class.add_stu('吴彦祖')
class.add_stu('古天乐')

class.add_stu('渣渣辉')

print('class是否是可迭代对象:', isinstance(class, Iterable))

# iter(class)
for stu in class:

    print('学生有:', stu)

结果:

class 是否是可迭代对象: True
mit 是否是迭代器: True
学生有: 吴彦祖
学生有: 古天乐
学生有: 渣渣辉

5.合并可迭代器和可迭代对象

demo:

from collections import Iterable
from collections import Iterator

class PythonClass(object):

    def __init__(self, name):
        self.name = name  # 班级的名字
        self.students = []  # 班级里的学生信息
        self.current_index = 0

    def add_stu(self, stu):
        """添加一个学生"""
        self.students.append(stu)

    def __iter__(self):
        return self  # 当前对象就是迭代器,直接在 __iter__ 返回即可

    def __next__(self):
        """__next__ 必须要有返回值,供 for 循环使用"""
        if self.current_index < len(self.students):
            stu = self.students[self.current_index]
            self.current_index += 1
            return stu
        else:
            # 没有数据了
            self.current_index = 0  # 当前对象是迭代器,一轮遍历结束要把下标位置归零
            raise StopIteration()

class= PythonClass('奥数班')

class.add_stu('吴彦祖')
class.add_stu('古天乐')
class.add_stu('渣渣辉')

print('class是否是可迭代对象:', isinstance(class, Iterable))

# iter(class)
for stu in class:
    print('学生有:', stu)

print('-----------')
for stu in class:

    print('学生有:', stu)

结果:

class 是否是可迭代对象: True
学生有: 吴彦祖
学生有: 古天乐
学生有: 渣渣辉
-----------
学生有: 吴彦祖
学生有: 古天乐

学生有: 渣渣辉

6. 可迭代器来处理斐波那契数列

demo:

class Nums(object):
    def __init__(self, num):
        self.num = num  # 要创建的数字个数
        self.a = 0
        self.b = 1
        self.current_index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_index < self.num:
            res = self.a
            self.a, self.b = self.b, self.a+self.b
            self.current_index += 1
            return res
        else:
            self.current_index = 0
            raise StopIteration

n = Nums(10)

for x in n:
    print(x)

print(list(n)) # 可迭代对象可以转换为列表和元组类型

print(tuple(n))

结果:

0
1
1
2
3
5
8
13
21
34
[55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
(6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229)

7.生成器及产生方法

利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器

方法一:只要把一个列表生成式的 [ ] 改成 ( )

demo:

In [15]: L = [ x*2 for x in range(5)]

In [16]: L
Out[16]: [0, 2, 4, 6, 8]

In [17]: G = ( x*2 for x in range(5))

In [18]: G
Out[18]: <generator object <genexpr> at 0x7f626c132db0>
创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。
demo:
In [19]: next(G)
Out[19]: 0

In [20]: next(G)
Out[20]: 2

方法二:

在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在def中有yield关键字的 就称为 生成器

demo: 

def create_num(num):
    a,b = 0,1
    for i in range(num):
        # print(a)

        yield a  # 只要函数里出现 yield 关键字,就会变成生成器。当生成器遇到 yield 会返回到next,并返回yield 后的数据

        # return '呵呵打'

        a,b = b, a+b

gen = create_num(5)
print(dir(gen))

# print(next(gen))  # 当遇到next会进入函数执行
# print(next(gen))  # 当再次调用 next ,会从之前遇到 yield 处的继续运行
# print(next(gen))
# print(next(gen))
# print(next(gen))
# print(next(gen))

for x in gen:

    print(x)

结果:
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
0
1
1
2

3

注意:

    (1) 生成器里的return会导致立即结束,抛出 StopIteration 异常,如果 return 后有数据,则会作为异常描述信息

    (2)使用一个生成器创建多个对象,对象间是彼此独立

8. 生成器使用send获取数据

def create_num(num):

    a,b = 0,1
    for i in range(num):
        x = yield a  # yield 之后的数据会在冻结代码的时候返回给 send
        print('x=',x)
        a,b = b, a+b

gen = create_num(5)

# tmp = next(gen)
tmp = gen.send(None)  # 如果在第一次使用生成器的时候调用 send,必须传递 None
print(tmp)

tmp = gen.send(None)  # send 在激活生成器的时候,可以传递一个数据给 yield
print(tmp)

tmp = gen.send('haha')
print(tmp)

tmp = next(gen)

print(tmp)

9. yield实现多任务效果

import time

def work1():
    while True:
        print('正在扫地~~~')
        time.sleep(1)
        yield

def work2():
    while True:
        print('正在搬砖~~~')
        time.sleep(1)
        yield

w1 = work1()
w2 = work2()

while True:
    next(w1)
    next(w2)

# 协程,在一个任务队列上不断的切换执行多个函数,协程必定是并发执行

10. greenlet

demo简单实用:

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中运行
gr1.switch()

demo2实现协程:

import gevent  # 1. 导入模块
import time

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5) # gevent 会自动在遇到耗时操作的时候切换到另一个任务,但是必须使用 gevent 提供的方法

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

g1 = gevent.spawn(f, 5)  # 2. 创建任务对象
g2 = gevent.spawn(f2, 5)

# 主线程不会等待gevent的任务运行结束
g1.join()
g2.join()

demo3:

import gevent
import time
from gevent import monkey

monkey.patch_all()  # 为当前文件的代码打补丁

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

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

# 一次性为所有的任务使用 join
gevent.joinall([
    gevent.spawn(f,5),
    gevent.spawn(f2,5)
])




猜你喜欢

转载自blog.csdn.net/kun1280437633/article/details/80325984