Python复习笔记(八)迭代器和生成器和协程

1. 迭代器

1.1 可迭代对象

  • 判断xxx_obj是否可以迭代

  • 在第1步成立的前提下,调用 iter 函数得到 xxx_obj 对象的 __iter__ 方法的返回值

  • __iter__ 方法的返回值是一个迭代器

  • 如果想要一个对象称为一个 可以迭代的对象,即可以使用for,必须实现 __iter__方法

  • __iter__ 中必须返回对象的引用【要这个对象有__iter____next__方法, 实际上取的__next__的返回值】

  • 迭代器结束,需要抛出一个 StopIteration 异常。

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):
        # 如果想要一个对象称为一个 可以迭代的对象,即可以使用for,必须实现 __iter__方法
        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

classmate = Classmate()


classmate.add("王1")
classmate.add("李2")
classmate.add("张3")

# print("classmate是否是可以迭代的对象: ", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("classmate_iterator是否是迭代器: ", isinstance(classmate_iterator, Iterator))

# iter
# print(next(classmate_iterator))
#
for name in classmate:
    print(name)
    time.sleep(1)

王1
李2
张3

1.2 调用自己的__next__方法

from collections import Iterable
from collections import Iterator
import time


class Classmate(object):
    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      # 调用返回对象的__next__方法(这里调用自己的__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   # 停止迭代


classmate = Classmate()

classmate.add("王1")
classmate.add("李2")
classmate.add("张3")

# print("classmate是否是可以迭代的对象: ", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("classmate_iterator是否是迭代器: ", isinstance(classmate_iterator, Iterator))

# iter
# print(next(classmate_iterator))
#
for name in classmate:
    print(name)
    time.sleep(1)

王1
李2
张3

生成斐波那契数列

class Fibnacci(object):
    def __init__(self, all_num):
        self.all_nums = 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_nums:
            ret = self.a

            self.a, self.b = self.b, self.a+self.b
            self.current_num += 1

            return ret
        else:
            raise StopIteration


fibo = Fibnacci(10)

for num in fibo:
    print(num)

2. 生成器--一种特殊迭代器

2.1 创建生成器1:()

2.2 创建生成器2:yield

def create_num(all_num):
    # a = 1
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a              # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        a, b = b, a + b
        current_num += 1


if __name__ == '__main__':
    # 如果在调用create_num的时候,发现这个函数有yield,此时不是调用函数,而是创建一个生成器对象
    obj = create_num(10)
    for num in obj:
        print(num)

注意:yield的工作流程

2.3 两个生成器之间没有影响

2.4 通过异常判断生成器已结束

2.5 生成器获得return的值

 

2.6 send使用--启动生成器

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        res = yield a
        print(">>>>ret>>>>", res)
        a, b = b, a + b
        current_num += 1


if __name__ == '__main__':
    obj = create_num(4)

    # obj.send(None) # send一般不会放到第一次启动生成器,如果非要如此,传递None

    ret = next(obj)
    print(ret)

    ret = obj.send("hhhhh")
    print(ret)

    # send里面的数据,会传递给第5行,当作yield a的结果,然后res保存这个结果..
    # send的结果是下一次调用yield时,yield后面的值
    ret = obj.send(None)
    print(ret)

    ret = obj.send(None)
    print(ret)

 注意:send不要放第一次

 

2.7 yield和return区别

yield可以暂停函数执行,且下一次执行时候恢复

2.8 迭代器和生成器作用

  • 迭代器: 减少内存空间, 能实现循环
  • 生成器: 能让一个函数看上去能暂停执行
  • 都是保证生成数据代码, 不是保存结果

生成器(yield): 实现多任务 !

3. 多任务-协程(yield执行)

进程占资源最多, 其次线程, 协程占资源最少!

#!/bin/python3
# -*- coding=utf-8 -*-

import time

def task_1():
    while True:
        print("------1-------")
        time.sleep(0.1)
        yield 


def task_2():
    while True:
        print("------2------")
        time.sleep(0.2)
        yield 


def main():
   t1 = task_1()
   t2 = task_2()
   while True:
       next(t1)
       next(t2)


if __name__ == "__main__":
    main()

并行: 有两个任务, 但是有四个CPU的核, 一个任务占一个核, 每个都在做

并发: 有很多任务, 但是只有两个核, 所以 交替执行

4. greenlet实现多任务(核心还是yield)

#!/bin/python3
# -*- encoding=utf-8 -*-

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

 5. gevent实现协程(更强大,常用)

#!/bin/python3
# -*-encoding=utf-8-*-

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)

print("----1-----")
g1 = gevent.spawn(f1, 5)
print("----2-----")
g2 = gevent.spawn(f2, 5)
print("----3-----")
g3 = gevent.spawn(f3, 5)
print("----4-----")


g1.join()
g2.join()
g3.join()

gevent遇到延时操作就切换, 利用了等待耗时的操作, 去做其他的事情

如下: 加入monkey.patch_all()则无须将 time.sleep()改成 gevent.sleep()

#!/bin/python3
# -*-encoding=utf-8-*-

import gevent
from gevent import monkey 
import time 

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)

print("----1-----")
g1 = gevent.spawn(f1, 5)
print("----2-----")
g2 = gevent.spawn(f2, 5)
print("----3-----")
g3 = gevent.spawn(f3, 5)
print("----4-----")


g1.join()
g2.join()
g3.join()

如下: 将需要join的代码, 写成列表, 简洁

#!/bin/python3
# -*-encoding=utf-8-*-

import gevent
from gevent import monkey 
import time 

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)

gevent.joinall([
    gevent.spawn(f1, 5), 
    gevent.spawn(f2, 5), 
    gevent.spawn(f3, 5)
    ])  

6. 并发下载器

#!/bin/python3
#-*- encoding=utf-8 -*-

import gevent 
import urllib.request 
from gevent import monkey


monkey.patch_all()

def downloader(img_name, img_url):

    req = urllib.request.urlopen(img_url)

    img_content = req.read()
      
    with open("./img/"+ img_name, "wb") as f:
        f.write(img_content)

def main():
    gevent.joinall([
            gevent.spawn(downloader, "1.jpg", 'https://rpic.douyucdn.cn/asrpic/190417/5440020_3968619_65b10_2_2142.jpg'),
            gevent.spawn(downloader, '2.png', "https://rpic.douyucdn.cn/asrpic/190417/594613_2143.png")
        ])  


if __name__=="__main__":
    main()

7. 进程/线程/协程对比

  • 进程: 耗费资源最多, 进程里一定有一个线程, 默认线程称为主线程。进程是资源分配的单位。(最稳定, 耗费资源最多)

  • 线程: 线程是操作系统调度的单位. 线程切换需要的资源一般, 效率一般 (不考虑GIL情况) 

  • 协程: 协程切换任务资源很小, 效率高;

    • 特点: 在等待某个资源到来 期间, 去执行其他代码....多线程里有很多网络堵塞, 推荐先用协程 !

  • 多进程、多线程根据cpu核数不一样 可能是并行的, 但是协程是在一个线程中, 所以是并发的!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-03-09 20:55  douzujun  阅读(298)  评论(0编辑  收藏  举报