31天Python入门——第12天:迭代器·生成器底层竟是这样子的

在这里插入图片描述

你好,我是安然无虞。

文章目录

    • 生成器
      • yield语句
      • 使用生成器
      • 对于 可迭代对象、迭代器、生成器的理解
      • 生成器的工作原理

在这里插入图片描述

生成器

生成器是一种特殊类型的函数,可以按需生成一系列的值,而不是一次性将所有值都计算出来并存储在内存中. 生成器可以大大节省内存消耗,特别适用于处理大型数据集或无限序列. -> 比如斐波那契数列就是可以生成的无限序列.

def gen_data():
  data_list = []
  # 模拟在data_list当中添加一百万条数据
  for i in range(1000000):
    data_list.append(i)
  return data_list

data_list = gen_data()
for data in data_list:
  print(data)

# 这里的实现方式是非常占用内存的.

将上面的代码改成采用生成器的方式:

def gen_data():
    # 生成器函数
    # 模拟在data_list当中添加一百万条数据.
    for i in range(1000000):
        yield i
        # return i # 直接return这个函数就结束了, 使用yield不会结束, 仍然会执行


# 生成器对象 = 生成器函数()
data_lst = gen_data()
print(data_lst)

for data in data_lst:
    print(data)

yield语句

生成器函数是一种定义生成器的方法。

它使用关键字yield来定义生成器的每个值。

当生成器函数被调用时,它不会立即执行函数体的所有代码,而是在每次调用生成器的__next__()方法时执行一部分代码,并在遇到yield语句时就返回一个值

yieldreturn 在 Python 中是用于返回值的两个关键字,它们在功能和用法上有一些区别:

  • return 关键字在函数内部,用于将值从函数中返回给调用者,并终止函数的执行。在函数中执行 return 后,函数将立即结束,并返回指定的值。函数只能 return 返回一次,并且返回的值可以被接收并使用。
  • yield 关键字用于生成器函数中,用于定义一个生成器函数,并将值产生给调用者。生成器函数是一种特殊的函数,可以通过 yield 关键字将多个值逐步产生,而不是一次性返回所有结果。当生成器函数执行到 yield 语句时,函数会暂停执行并将值返回给调用者,但函数的状态会被保存,以便下次从暂停的地方继续执行。生成器函数可以多次使用 yield 来生成多个值,并且可以在需要时进行迭代处理。

总结区别:

  • return 用于函数中,一次性返回最终结果并终止函数的执行。
  • yield 用于生成器函数中,逐步产生值并暂停函数的执行,可以多次使用,并且在需要时进行迭代处理。

使用生成器

生成器通过next()函数或迭代语句来逐个获取生成器的值。生成器会记录它的内部状态,因此每次调用next()函数时会从上次停止的地方继续执行。

# 生成器函数
def test(num):
    while num > 0:
        yield num
        num -= 1


# 生成器是特殊的迭代器.
gen = test(5)
num = next(gen)
print(num) # 5

num1 = next(gen)
print(num1) # 4
num2 = next(gen)
print(num2) # 3
num3 = next(gen)
print(num3) # 2
num4 = next(gen)
print(num4) # 1

# 如果在多调用1次, 就会出错
num5 = next(gen)
print(num5)

对于 可迭代对象、迭代器、生成器的理解

a_list = [1,2,3,4,5] # 列表就是可迭代对象
b_list = iter(a_list) # 通过调用.iter()就将可迭代对象转化成为迭代器了

# 而生成器就是特殊的迭代器

# 只有成为迭代器才能调用这里的 .next() 方法
# 所以下面的代码:
ele1 = next(a_list)
print(ele1) # 会返回错误 - 表示 'list不是一个迭代器iterator'

ele2 = next(b_list)
print(ele2) # 1

生成器的工作原理

生成器的工作原理是通过迭代器协议来实现的。生成器函数使用yield语句将值生成出来,并在生成值后暂停执行,将控制权交还给调用者。当调用者请求下一个值时,生成器函数会从上次暂停的地方继续执行,生成下一个值,并再次暂停。

这种逐步生成值的方式使得生成器可以有效地处理大量的数据或无限的序列,而不会一次性占用大量的内存空间。

# 生成器函数
def test(num):
    while num > 0:
        t = yield num # t 就是10
        num -= 1

# 生成器是特殊的迭代器.
gen = test(5)
# 我们可以跟生成器函数进行交互
num = next(gen) 
print(num) # 5
gen.send(10)
num1 = next(gen)
print(num1) # 3

# ------------------------------
# 上面的问题是: 这里的 4 哪里去了?
# 其实是在执行 gen.send(10) 的时候消耗一次 .next()
# 所以其实 num = next(gen) 就相当于 gen.send(None)

# 如果想打印出消失的4, 可以这样改写代码:
gen = test(5)
num = next(gen) # 等价于 gen.send(None)
print(num) # 5
n = gen.send(10)
print(n) # 4
num1 = next(gen)
print(num1) # 3

# 使用gen.send()就是为了和生成器函数进行交互的
def test(num):
    while num > 0:
        t = yield num
        print('t的值: ', t)
        if t is not None:
            num = t
        num -= 1

# 生成器是特殊的迭代器
gen = test(5)
num = gen.send(None) # 其实就相当于num=next(gen)
print(num) # 5

n = gen.send(10)
print(n) # 9

生成器的工作原理如下:

  1. yield 表达式的作用:在生成器函数中,yield 是一个表达式,它会暂停生成器的执行,并将 yield 后面的值返回给调用者。同时,yield 表达式本身会返回一个值,这个值就是通过 send() 方法发送进来的值。
  2. send() 方法的作用send() 方法用于向生成器发送一个值。当生成器执行到 yield 表达式时,send() 方法发送的值会被赋值给 yield 表达式的结果。

所以上面的代码中, 当调用 gen.send(10) 时,10 这个值会被赋值给生成器函数中的变量 t,即 t 的值会变成 10

所以可以使用.send()和生成器函数进行交互.

再补充一个知识点就是在生成器函数中的return:

# 生成器函数
def test(num):
  while num > 0:
    t = yield num
    if t is not None:
      return
    num -= 1
    # 生成器中的return并不是给出返回值, 而是抛出异常, 相当于 raise StopIte ration
    # 生成器当中的return还有一个特点就是终止这个生成器
    
gen = test(5)
num = gen.send(None)
print(num)
n = gen.send(10)
print(n)

# 执行该程序就会抛出异常

生成器表达式

生成器表达式是一种简洁的语法,用于快速创建生成器。它的语法与列表推导式类似,但使用圆括号而不是方括号。生成器表达式可以在需要时逐个生成值,而不是一次性生成所有值。

下面是一个生成器表达式的示例,它生成了一个包含1到10之间所有偶数的生成器:

even_numbers = (x for x in range(1, 11) if x % 2 == 0)

练习: 生成器实现斐波那契数列

# 补充说明: Python当中交换两个数的值非常简单就是:
a = 10
b = 20

a, b = b, a
# 生成器实现斐波那契数列.
# 1 1 2 3 5 8 13  每次生成的数, 都等于前2个数的和.
def gen_fib():
    a, b = 0, 1
    while True:
        yield b
        a, b = b, a + b


fib = gen_fib()
for i in fib:
    print(i)

# 列表推导式生成列表
even_numbers1 = [x for x in range(1, 11) if x % 2 == 0] 
print(even_numbers1)

# 生成器表达式生成生成器
even_numbers2 = (x for x in range(1, 11) if x % 2 == 0)
print(even_numbers2)

生成器的优势

使用生成器有以下几个优势:

  • 节省内存消耗:生成器按需生成值,而不需要一次性生成所有值,可以节省大量内存消耗,尤其适用于处理大型数据集或无限序列。
  • 惰性计算:生成器在需要时逐个生成值,可以提供惰性计算的能力,避免不必要的计算。
  • 无限序列:生成器可以轻松地表示无限序列,如斐波那契数列或素数序列,而无需事先计算所有值。
  • 迭代器的统一接口:生成器实现了迭代器协议,可以使用相同的方式处理生成器和其他迭代器。

生成器是Python中非常强大和灵活的工具,可以优化代码的性能和内存消耗,并简化处理大型数据集和无限序列的操作。通过掌握生成器的概念和使用方法,可以提高代码的效率和可读性.

在后面的面向对象章节中详细讲解前面提到的迭代器和可迭代对象.

遇见安然遇见你,不负代码不负卿。
谢谢老铁的时间,咱们下篇再见~

你可能感兴趣的:(Python手把手教程,python,职场和发展,后端,pyqt)