Python 高级 7

迭代、迭代器、生成器、协程、yield、greenlet、gevent、进程线程协程对比、gevent多任务图片下载

1.迭代

<1>迭代的概念

使用for循环遍历取值的过程叫做迭代,比如:使用for循环遍历列表获取值的过程

for value in [2, 3, 4]:

print(value)

<2>可迭代对象

使用for循环遍历取值的对象叫做可迭代对象, 比如:列表、元组、字典、集合、range、字符串

<3>判断对象是否是可迭代对象

先导入collections包中的Iterable类

isinstance的使用

isinstance(o, t), object type

isinstance(对象,类型) # 判断对象是否是指定类型,返回一个布尔类型

from collections import Iterable

# 判断对象是否是指定类型

result = isinstance((3, 5), Iterable)

print("元组是否是可迭代对象:", result)

# 提示: 以后还根据对象判断是否是其它类型,比如以后可以判断函数里面的参数是否是自己想要的类型

result = isinstance(5, int)

print("整数是否是int类型对象:", result)

<4>自定义可迭代对象

自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象

自定义可迭代类型代码

from collections import Iterable

# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象

class MyList(object):

    def __init__(self):

        self.my_list = list()

    # 添加指定元素

    def append_item(self, item):

        self.my_list.append(item)

    def __iter__(self):

        # 可迭代对象的本质:遍历可迭代对象的时候其实获取的是可迭代对象的迭代器, 然后通过迭代器获取对象中的数据

# 可迭代对象的本质: 是通过迭代器帮助可迭代对象依次迭代对象中的每一个数据,真正完成获取数据的操作是通过迭代器完成的

        pass

my_list = MyList()

my_list.append_item(1)

my_list.append_item(2)

result = isinstance(my_list, Iterable)

print(result)

for value in my_list:

    print(value)

遍历可迭代对象依次获取数据需要迭代器

小结

在类里面提供一个__iter__创建的对象是可迭代对象,可迭代对象是需要迭代器完成数据迭代的。

2.迭代器

<1>自定义迭代器对象

自定义迭代器对象: 在类里面定义__iter__和__next__方法创建的对象就是迭代器对象

from collections import Iterable

from collections import Iterator

# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象

class MyList(object):

    def __init__(self):

        self.my_list = list()

    # 添加指定元素

    def append_item(self, item):

        self.my_list.append(item)

    def __iter__(self):

        # 可迭代对象的本质:遍历可迭代对象的时候其实获取的是可迭代对象的迭代器, 然后通过迭代器获取对象中的数据

        my_iterator = MyIterator(self.my_list)

        return my_iterator

# 自定义迭代器对象: 在类里面定义__iter__和__next__方法创建的对象就是迭代器对象

class MyIterator(object):

    def __init__(self, my_list):

        self.my_list = my_list

        # 记录当前获取数据的下标

        self.current_index = 0

        # 判断当前对象是否是迭代器

        result = isinstance(self, Iterator)

        print("MyIterator创建的对象是否是迭代器:", result)

    def __iter__(self):

        return self

    # 获取迭代器中下一个值

    def __next__(self):

        if self.current_index < len(self.my_list):

            self.current_index += 1

            return self.my_list[self.current_index - 1]

        else:

            # 数据取完了,需要抛出一个停止迭代的异常

            raise StopIteration

my_list = MyList()

my_list.append_item(1)

my_list.append_item(2)

result = isinstance(my_list, Iterable)

print(result)

for value in my_list:

    print(value)

<2>iter()函数与next()函数

iter函数: 获取可迭代对象的迭代器,会调用可迭代对象身上的__iter__方法

next函数: 获取迭代器中下一个值,会调用迭代器对象身上的__next__方法

# 自定义可迭代对象: 在类里面定义__iter__方法创建的对象就是可迭代对象

class MyList(object):

    def __init__(self):

        self.my_list = list()

    # 添加指定元素

    def append_item(self, item):

        self.my_list.append(item)

    def __iter__(self):

        # 可迭代对象的本质:遍历可迭代对象的时候其实获取的是可迭代对象的迭代器, 然后通过迭代器获取对象中的数据

        my_iterator = MyIterator(self.my_list)

        return my_iterator

# 自定义迭代器对象: 在类里面定义__iter__和__next__方法创建的对象就是迭代器对象

# 迭代器是记录当前数据的位置以便获取下一个位置的值

class MyIterator(object):

    def __init__(self, my_list):

        self.my_list = my_list

        # 记录当前获取数据的下标

        self.current_index = 0

    def __iter__(self):

        return self

    # 获取迭代器中下一个值

    def __next__(self):

        if self.current_index < len(self.my_list):

            self.current_index += 1

            return self.my_list[self.current_index - 1]

        else:

            # 数据取完了,需要抛出一个停止迭代的异常

            raise StopIteration

# 创建了一个自定义的可迭代对象

my_list = MyList()

my_list.append_item(1)

my_list.append_item(2)

# 获取可迭代对象的迭代器

my_iterator = iter(my_list)

print(my_iterator)

# 获取迭代器中下一个值

# value = next(my_iterator)

# print(value)

# 循环通过迭代器获取数据

while True:

    try:

        value = next(my_iterator)

        print(value)

    except StopIteration as e:

        break

<3>for循环的本质

遍历的是可迭代对象

  for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

遍历的是迭代器

  for item in Iterator 循环的迭代器,不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

for循环内部自动捕获停止迭代的异常,而while循环内部没有自己捕获

最终取值操作都是通过迭代器完成的

<4>迭代器的应用场景

我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。

举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

现在我们想要通过for...in...循环来遍历迭代斐波那契数列中的前n个数。那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。

迭代器完成斐波那契的好处:

  节省内存空间,因为每次根据算法只生成一个值

  生成数列的个数没有上限控制

class Fibonacci(object):

    def __init__(self, num):

        # num:表示生成多少fibonacci数字

        self.num = num

        # 记录fibonacci前两个值

        self.a = 0

        self.b = 1

        # 记录当前生成数字的索引

        self.current_index = 0

    def __iter__(self):

        return self

    def __next__(self):

        if self.current_index < self.num:

            result = self.a

            self.a, self.b = self.b, self.a + self.b

            self.current_index += 1

            return result

        else:

            raise StopIteration

fib = Fibonacci(5)

# value = next(fib)

# print(value)

for value in fib:

    print(value)

小结

迭代器的作用就是是记录当前数据的位置以便获取下一个位置的值

3.生成器

<1>生成器的概念

生成器是一类特殊的迭代器,它不需要再像上面的类一样写__iter__()和__next__()方法了, 使用更加方便,它依然可以使用next函数和for循环取值

<2>创建生成器方法1

  第一种方法很简单,只要把一个列表推导式的 [ ] 改成 ( )

my_list = [i * 2 for i in range(5)]

print(my_list)

# 创建生成器

my_generator = (i * 2 for i in range(5))

print(my_generator)

# next获取生成器下一个值

# value = next(my_generator)

# print(value)

for value in my_generator:

    print(value)

<3>创建生成器方法2

在def函数里面看到有yield关键字那么就是生成器

def fibonacci(num):

    a = 0

    b = 1

    # 记录生成fibonacci数字的下标

    current_index = 0

    print("--11---")

    while current_index < num:

        result = a

        a, b = b, a + b

        current_index += 1

        print("--22---")

        # 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行

        yield result

        print("--33---")

fib = fibonacci(5)

value = next(fib)

print(value)

value = next(fib)

print(value)

value = next(fib)

print(value)

# for value in fib:

#    print(value)

在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。

简单来说:只要在def中有yield关键字的 就称为 生成器

<4>生成器使用return关键字

def fibonacci(num):

    a = 0

    b = 1

    # 记录生成fibonacci数字的下标

    current_index = 0

    print("--11---")

    while current_index < num:

        result = a

        a, b = b, a + b

        current_index += 1

        print("--22---")

        # 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行

        yield result

        print("--33---")

        return "嘻嘻"

fib = fibonacci(5)

value = next(fib)

print(value)

# 提示: 生成器里面使用return关键字语法上没有问题,但是代码执行到return语句会停止迭代,抛出停止迭代异常

# 在python3里面可以使用return关键字,python2不支持

# return 和 yield的区别

# yield: 每次启动生成器都会返回一个值,多次启动可以返回多个值,也就是yield可以返回多个值

# return: 只能返回一次值,代码执行到return语句就停止迭代

try:

    value = next(fib)

    print(value)

except StopIteration as e:

    # 获取return的返回值

    print(e.value)

提示:

  生成器里面使用return关键字语法上没有问题,但是代码执行到return语句会停止迭代,抛出停止迭代异常

  在python3里面可以使用return关键字,python2不支持

<5>yield和return的对比

  使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)

  代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行

  每次启动生成器都会返回一个值,多次启动可以返回多个值,也就是yield可以返回多个值

  return只能返回一次值,代码执行到return语句就停止迭代,抛出停止迭代异常

<6>使用send方法启动生成器并传参

send方法启动生成器的时候可以传参数

def gen():

i = 0

while i<5:

temp = yield i

print(temp)

i+=1

next  和 send的区别:

  next函数启动生成器不能传入参数

  send方法启动生成器可以传入参数,但是第一次只能传入None

注意:

如果第一次启动生成器使用send方法,那么参数只能传入None,一般第一次启动生成器使用next函数

小结

  生成器创建有两种方式,一般都使用yield关键字方法创建生成器

  yield特点是代码执行到yield会暂停,把结果返回出去,再次启动生成器在暂停的位置继续往下执行

4.协程

<1>协程的概念

协程,又称微线程,纤程,也称为用户级线程,在不开辟线程的基础上完成多任务,也就是在单线程的情况下完成多任务,多个任务按照一定顺序交替执行 通俗理解只要在def里面只看到一个yield关键字表示就是协程

协程是也是实现多任务的一种方式

协程yield的代码实现

import time

def work1():

    while True:

        print("----work1---")

        yield

        time.sleep(0.5)

def work2():

    while True:

        print("----work2---")

        yield

        time.sleep(0.5)

def main():

    w1 = work1()

    w2 = work2()

    while True:

        next(w1)

        next(w2)

if __name__ == "__main__":

    main()

小结:

协程之间执行任务按照一定顺序交替执行

5.greenlet

<1>greentlet的介绍

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单

使用如下命令安装greenlet模块:

pip3 install greenlet

import time

import greenlet

# 任务1

def work1():

    for i in range(5):

        print("work1...")

        time.sleep(0.2)

        # 切换到协程2里面执行对应的任务

        g2.switch()

# 任务2

def work2():

    for i in range(5):

        print("work2...")

        time.sleep(0.2)

        # 切换到第一个协程执行对应的任务

        g1.switch()

if __name__ == '__main__':

    # 创建协程指定对应的任务

    g1 = greenlet.greenlet(work1)

    g2 = greenlet.greenlet(work2)

    # 切换到第一个协程执行对应的任务

    g1.switch()

6.gevent

<1>gevent的介绍

greenlet已经实现了协程,但是这个还要人工切换,这里介绍一个比greenlet更强大而且能够自动切换任务的第三方库,那就是gevent。

gevent内部封装的greenlet,其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

安装

pip3 install gevent

<2>gevent的使用

import gevent

def work(n):

    for i in range(n):

        # 获取当前协程gevent.getcurrent()

        print(gevent.getcurrent(), i)

g1 = gevent.spawn(work, 5)

g2 = gevent.spawn(work, 5)

g3 = gevent.spawn(work, 5)

g1.join()

g2.join()

g3.join()

3个greenlet是依次运行而不是交替运行

<3>gevent切换执行

import gevent

def work(n):

    for i in range(n):

        # 获取当前协程

        print(gevent.getcurrent(), i)

        #用来模拟一个耗时操作,注意不是time模块中的sleep

        gevent.sleep(1)

g1 = gevent.spawn(work, 5)

g2 = gevent.spawn(work, 5)

g3 = gevent.spawn(work, 5)

g1.join()

g2.join()

g3.join()

<4>给程序打补丁

gevent默认不会认为系统的耗时操作是耗时的,不识别time.sleep,accept,recv等,需要打补丁

import gevent

import time

from gevent import monkey

# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,accept,recv,网络请求延时等

monkey.patch_all()

# 任务1

def work1(num):

    for i in range(num):

        print("work1....")

        time.sleep(0.2)

        # gevent.sleep(0.2)

# 任务1

def work2(num):

    for i in range(num):

        print("work2....")

        time.sleep(0.2)

        # gevent.sleep(0.2)

if __name__ == '__main__':

    # 创建协程指定对应的任务

    g1 = gevent.spawn(work1, 3)

    g2 = gevent.spawn(work2, 3)

    # 主线程等待协程执行完成以后程序再退出

    g1.join()

    g2.join()

<5>注意

  当前程序是一个死循环并且还能有耗时操作,就不需要加上join方法了,因为程序需要一直运行不会退出

示例代码

import gevent

import time

from gevent import monkey

# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时

monkey.patch_all()

# 任务1

def work1(num):

    for i in range(num):

        print("work1....")

        time.sleep(0.2)

        # gevent.sleep(0.2)

# 任务1

def work2(num):

    for i in range(num):

        print("work2....")

        time.sleep(0.2)

        # gevent.sleep(0.2)

if __name__ == '__main__':

    # 创建协程指定对应的任务

    g1 = gevent.spawn(work1, 3)

    g2 = gevent.spawn(work2, 3)

    while True:

        print("主线程中执行")

        time.sleep(0.5)

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

<1>进程、线程、协程之间的关系

  一个进程至少有一个线程,进程里面可以有多个线程

  一个线程里面可以有多个协程

<2>进程、线程、线程的对比

  进程是系统资源分配的基本单位

  线程是操作系统调度的基本单位

  进程切换需要的资源最大,效率很低

  线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)

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

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

小结

  进程、线程、协程都是可以完成多任务的,可以根据自己实际开发的需要选择使用

  由于线程、协程需要的资源很少,所以使用线程和协程的几率最大

  开辟协程需要的资源最少

  协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

  进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

  线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)

8.gevent多任务图片下载

<1>多任务图片下载的示例代码

import gevent

import urllib.request # 网络请求模块

from gevent import monkey

# 打补丁: 让gevent使用网络请求的耗时操作,让协程自动切换执行对应的下载任务

monkey.patch_all()

# 根据图片地址下载对应的图片

def download_img(img_url, img_name):

    try:

        print(img_url)

        # 根据图片地址打开网络资源数据

        response = urllib.request.urlopen(img_url)

        # 创建文件把数据写入到指定文件里面

        with open(img_name, "wb") as img_file:

            while True:

                # 读取网络图片数据

                img_data = response.read(1024)

                if img_data:

                    # 把数据写入到指定文件里面

                    img_file.write(img_data)

                else:

                    break

    except Exception as e:

        print("图片下载异常:", e)

    else:

        print("图片下载成功: %s" % img_name)

if __name__ == '__main__':

    # 准备图片地址

    img_url1 = "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=551346117,2593226454&fm=27&gp=0.jpg"

    img_url2 = "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=829730016,3409799239&fm=27&gp=0.jpg"

    img_url3 = "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1815077192,817368579&fm=27&gp=0.jpg"

    # 创建协程指派对应的任务

    g1 = gevent.spawn(download_img, img_url1, "1.jpg")

    g2 = gevent.spawn(download_img, img_url2, "2.jpg")

    g3 = gevent.spawn(download_img, img_url3, "3.jpg")

    # 主线程等待所有的协程执行完成以后程序再退出

    gevent.joinall([g1, g2, g3])

依次根据图片地址去下载,但是收到数据的先后顺序不一定与发送顺序相同,这也就体现出了异步,即不确定什么时候会收到数据,顺序不一定


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容