对Python生成器的理解

来源:互联网 发布:看门狗pc优化补丁 编辑:程序博客网 时间:2024/05/16 18:03

下午看了一点生成器的内容,这部分算是python的基础吧。所以我就不放在我的进阶系列了。正好吃饱饭没事做,就来写写我对生成器的一点浅薄理解吧。 ——无聊的前言


一.为什么要有生成器

秉着先问为什么,再问怎么做的原则,我们来看看为什么python会添加生成器这个功能。

python在数据科学领域可以说是很火。我想有一部分的功劳就是它的生成器了吧。

我们知道我们可以用列表储存数据,可是当我们的数据特别大的时候建立一个列表的储存数据就会很占内存的。这时生成器就派上用场了。它可以说是一个不怎么占计算机资源的一种方法。


二.简单的生成器

我们可以用列表推导(生成式)来初始化一个列表:

list5 = [x for x in range(5)]print(list5)   #output:[0, 1, 2, 3, 4]

我们用类似的方式来生成一个生成器,只不过我们这次将上面的[ ]换成( ):

gen = (x for x in range(5))print(gen) #output: <generator object <genexpr> at 0x0000000000AA20F8>

看到上面print(gen) 并不是直接输出结果,而是告诉我们这是一个生成器。那么我们要怎么调用这个gen呢。
有两种方式:
第一种:

for item in gen:    print(item)#output:01234

第二种:

print(next(gen))#output:0print(next(gen))#output:1print(next(gen))#output:2print(next(gen))#output:3print(next(gen))#output:4print(next(gen))#output:Traceback (most recent call last):StopIteration

好了。现在可以考虑下背后的原理是什么了。
从第一个用for的调用方式我们可以知道生成器是可迭代的。更准确的说法是他就是个迭代器。
我们可以验证一下:

from collections import Iterable, Iteratorprint(isinstance(gen, Iterable))#output:Trueprint(isinstance(gen, Iterator))#output:True

str,list,tuple,dict,set这些都是可迭代的,就是可用for来访问里面的每一个元素。但他们并不是迭代器。

那什么是迭代器?
我们可以理解为我们平时做一件事的步骤。

比如我们泡茶:
首先,得去煮水。
然后,拿出茶具,和茶叶
接着,水开了,就开始泡茶
最后,就是品茶了。

假如我们定义了一个泡茶的函数(迭代器),然后将泡茶步骤的方法封装进这个函数。每一次调用这个函数就返回一个步骤,并保存好当前执行到哪个状态。如果中途有事,比如我们执行到步骤二的时候突然去接了个电话,回来调用这个函数就会得到步骤三(水开了,就开始泡茶),也就是状态保存好了。我们可以执行这个泡茶函数直到调用完所有步骤为止。
定义一个方法,这个方法是一步步执行的,并能保存状态,这就是迭代器。

回到上面上面第二种访问方法中,到第六个print(next(gen))时,系统告诉我们Traceback (most recent call last): StopIteration。也就是gen迭代到最后了,无法继续迭代了。

而生成器本身就是一个迭代器
我们在内部封装好了算法,并规定好在某个条件下就返回一个结果给调用者。(x for x in range(5))就是这样子实现的,并不是实现了(0,1,2,3,4)然后在一个个迭代出来,而是逐个生成。这就是为什么next(gen)可以作用了。


三.应用

1.前面说到生成器生成大量数据的时候可帮助系统节省内存。真的是这样吗?通过下面的代码感受下:

代码里面会运用到装饰器的原理,看不懂没关系后文会解释有什么作用。当然想了解的可以看我另一篇博文:Python进阶(四):浅析装饰器(decorator)@

import timedef get_time(func):    def wraper(*args, **kwargs):        start_time = time.time()        result = func(*args, **kwargs)        end_time = time.time()        print("Spend:", end_time - start_time)        return result    return wraper@get_timedef _list(n):    l1 = [list(range(n)) for i in range(n)]    del ge@get_timedef _generator(n):    ge = (tuple(range(n)) for i in range(n))    del t1_list(1000)_generator(1000)

哈哈,有人实在看不懂的话可以看下面的代码,与上面等效的

题外话:其实上面那个get_time函数是我自己平常经常要用到的一个功能。我把它写到一个我的工具包里了(造轮子),就是如果我想看看一段函数的执行时间(测试效率),就掉用这个包里的get_time装饰器,装饰一下就好了。所以建议大家如果发现有个功能经常要用到的话可以尝试下自己造轮子。这里我就直接贴上代码了

import timedef _list(n):    l1 = [list(range(n)) for i in range(n)]    del l1def _generator(n):    ge = (tuple(range(n)) for i in range(n))    del gestart_time = time.time()_list(1000)end_time = time.time()print("Spend:",end_time - start_time)start_time = time.time()_generator(1000)end_time = time.time()print("Spend:",end_time - start_time)

好了,运行代码我们可以看到:

Spend: 0.04300236701965332Spend: 0.0

分析下就可以知道,列表是将0-999都生成后放进一个列表里面了,所以用得时间比较多。
而生成器只是封装了算法,每次调用在去调用算法,这样做就可以做到节省内存了。

2.yield 关键词

好吧,前面只告诉我们用( )来创建一个生成器。如果我们想定义一个自己的生成器函数怎么办?用return好像不行。没关系,python有yield的关键词。其作用和return的功能差不多,就是返回一个值给调用者,只不过有yield的函数返回值后函数依然保持调用yield时的状态,当下次调用的时候,在原先的基础上继续执行代码,直到遇到下一个yield或者满足结束条件结束函数为止。

一个简单的例子:

def test():    yield 1    yield 2    yield 3t = test()print(next(t))#output:1print(next(t))#output:1print(next(t))#output:1print(next(t))#output:Traceback (most recent call last):StopIteration

好像并没啥卵用啊!骚年,存在即合理,python有生成器不是没有道理的。数学中有很多算法是无限穷举的(比如自然数),我们不可能一一穷举出来,所以生成器就可以帮助我们。
举个例子:杨辉三角。
这里写图片描述
这就是一个无限穷举的,我们可以将他的算法封装成生成器,需要的时候去生成就好,这样就不会占用大量的电脑内存资源。
下面给出代码:

def triangle():    _list, new_list = [], []    while True:        length = len(_list)        if length == 0:            new_list.append(1)        else:            for times in range(length + 1):                if times == 0:                    new_list.append(1)                elif times == length:                    new_list.append(1)                else:                    temp = _list[times - 1] + _list[times]                    new_list.append(temp)        yield new_list #返回值,然后挂起函数,等待下一次调用        _list = new_list.copy()#调用后会继续执行下去        new_list.clear()n = 0for result in triangle():    n += 1    print(result)    if n == 10:        break

结果:

[1][1, 1][1, 2, 1][1, 3, 3, 1][1, 4, 6, 4, 1][1, 5, 10, 10, 5, 1][1, 6, 15, 20, 15, 6, 1][1, 7, 21, 35, 35, 21, 7, 1][1, 8, 28, 56, 70, 56, 28, 8, 1][1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

好了,觉得写得好可以关注微信公众号:
这里写图片描述

原创粉丝点击