python基础(part16)--生成器

鄙人学习笔记
开发工具:Spyder



生成器generator

  • 定义

能够动态(循环一次计算一次返回一次)提供数据的可迭代对象。

  • 作用

在循环过程中,按照某种算法推算数据,不必创建容器,存储完整的结果,从而节省内存空间。数据量越大,优势越明显。

备注:以上生成器的操作,也称为惰性操作/延迟操作,通俗的讲就是在需要的时候才计算结果,而不是一次构建出所有结果。

生成器函数

  • 定义

生成器函数是含有yield语句的函数,生成器函数的返回值为生成器对象。只要我们的函数中有yield,那么这个函数就是生成器函数。

  • 语法

创建生成器函数:

def 函数名():
    ...
    yield 数据
    ...

调用生成器函数:

for 变量名 in 函数名():
    语句
  • 说明

①调用生成器函数将返回一个生成器对象,不执行函数体。
②yield翻译为”产生”或”生成”。

举个例子1(迭代器–>过渡–>生成器)

我们看一下,下面几行代码:

  • 运行说明

首先,当程序执行第24行代码,即调用生成器函数my_range()时,它不会去执行my_range()函数体里的内容,而是先返回给变量iter01一个生成器对象,之后程序会继续向下执行for循环语句:

当执行第25行语句时,for循环内部调用了__next__()方法,这时才开始执行my_range()函数体里的内容:

当执行到yield语句时,程序又返回了,程序将yield右边的start作为__next__()方法的返回值,给了变量item:



之后的程序运行内容,不言而喻。就不一一赘述了,有机会可以自己在python里,断点调试,观察一下~

举个例子2

我想在列表[1, 2, 5, 4, 6, 7, 9]中选出所有偶数,并且通过生成器函数来实现。

创建生成器函数:

def get_even(target):
    for item in target:
        if item % 2 == 0:
            yield item

备用1:我们每次写生成器函数时,都要思考yield要返回什么内容.
备用2:我们什么时候使用生成器函数呢?在我们的方法/函数需要向外返回多个结果时,可以使用生成器函数.

调用生成器函数:

list01 = [1, 2, 5, 4, 6, 7, 9]
iter01 = get_even(list01)

for item in iter01:
    print(item)

结果:

我们其实不用生成器函数,也可以拿到这个结果,比如以下代码。

创建函数:

def get_even01(target):
    result = []
    for item in target:
        if item %2 == 0:
            result.append(item)
    return result

调用函数:

list01 = [1, 2, 5, 4, 6, 7, 9]
iter01 = get_even01(list01)

for item in iter01:
    print(item)

结果:

这个结果,同上面利用生成器函数,得到的结果一样。

那么我们为啥要用生成器函数呢?他的优势是啥呢?

因为,如果调用get_even01()方法,则会执行get_even01()方法体,将所有结果存在内存中。但是,如果调用生成器函数get_even(), 则不会执行方法体,在内存中不会存储数据。我们用for循环,循环一次,也就是__next__()一次,计算一次,返回一次。即,需要一次,做一次。这种生成器函数的操作就叫做:惰性操作/延迟操作。
所以,在数据量非常大的情况下,如果不用生成器函数进行操作,则,我们的内存很快就会被占满。

内置生成器

我们先举两个例子。

举个例子1

我们看一段代码:


结果:

我们再写一个自定义函数my_enumerate,具有和上述例子中,enumerate函数所演示出来的相同的功能:

for循环测试一下:


结果:

嗯!一毛一样~

举个例子2

我们再看一段代码:

list01 = [0, 6, 7]
list02 = ["小白", "小黄", "大白"]

for item in zip(list01, list02):
    print(item)

结果:

若我们在列表list02中再加一个元素,使两个列表元素不相同:

结果:


我们再写一个自定义函数my_zip,具有和上述例子中,zip函数所演示出来的相同的功能(不考虑两个列表元素不相同的情况):

def my_zip(list01, list02):
    for index in range(len(list01)):
        yield (list01[index], list02[index])

for循环测试一下:

list01 = [0, 6, 7]
list02 = ["小白", "小黄", "大白"]

for item in my_zip(list01, list02):
    print(item)

结果:

注意:我们称这种必须由__next__()驱动的函数(enumerate() / zip()),叫做内置生成器。

枚举函数enumerate

  • 语法
for 变量 in enumerate(可迭代对象):
    语句

for 索引, 元素 in enumerate(可迭代对象):
    语句
  • 作用

遍历可迭代对象时,可以将索引与元素组合为一个元组。

zip

  • 语法
for 变量 in zip (可迭代对象1, 可迭代对象2….):
    语句
  • 作用

将多个可迭代对象中对应的元素组合成一个个元组,生成的元组个数由元素数量最小的可迭代对象决定。

生成器表达式

  • 定义

用推导式形式创建生成器对象。

  • 语法
变量 = (表达式 for 变量 in 可迭代对象 [if 真值表达式])

举个例子1

如果我们有一个列表list01=[2,3,4,6], 如下图所示:


我们想得到列表内的每个元素的平方,该咋整呢?

方式1:

方式2(列表推导式):


我们print一下result得到结果:

备注1:方式1的代码是列表推导式的传统写法
备注2:对于推导式,我们还学习了字典推到式,集合推导式。但是没有元组推导式,这是为什么捏?这是因为,列表、字典、集合都是可变对象,可以不断增加元素。而元组是不可变的。

我们再写一段代码:

这个可不是”元组推导式”(压根没有元组推导式),这是生成器表达式。

这时,我们再print一下result:

得到一个生成器对象。生成器的本质是一个惰性查找机制,要调用它,就要用__next__(), 也就是可以用for循环:

得到结果:

我们可以写一个与这个生成器表达式功能相同的生成器函数:

for循环:

得到结果:

嗯!与利用生成器表达式运行的结果相同。

这时候我们就想问:列表推导式和生成器表达式有啥区别?这个问题就相当于问:列表推导式的传统写法与生成器函数有啥不同?

:区别就是列表推导式的传统写法会用一个列表result存储所有结论,占用内存。而生成器函数是惰性操作,被调用一次__next__(), 才会计算并返回一次。

举个例子2

分别使用列表推导式和生成器表达式,获取列表[2, 3, 4, 5, 6]中,大于3的数据。

代码:

list01 = [2, 3, 4, 5, 6]

result01 = [item for item in list01 if item > 3]
result02 = (item for item in list01 if item > 3)

for item in result01:
    print(item)

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

for item in result02:
    print(item)

结果:

备注:别看列表推导式和生成器表达式的代码极其相似,但是他们内部却大有不同,【result01 = [item for item in list01 if item > 3]】是执行所有操作,保存所有结果;【result02 = (item for item in list01 if item > 3】是返回了一个生成器对象 。我们也可以通过断点调试,去加深理解。

发布了116 篇原创文章 · 获赞 22 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/m0_37422217/article/details/105152677