python中的生成器(generator)学习

生成器(Generator)是一种特殊的函数,可以用于迭代地生成一系列值,而不需要一次性生成所有值并将它们存储在内存中。生成器在需要时逐个生成值,并在生成值后暂停执行,保留函数的状态,以便下次调用时能够从停止的地方继续执行。

生成器函数使用 yield 语句来定义,而不是常规函数中的 return 语句。当生成器函数被调用时,它返回一个生成器对象,而不是立即执行函数体。每次调用生成器对象的 next() 方法时,生成器函数将从上次执行停止的位置继续执行,直到遇到下一个 yield 语句。生成器通过生成一个值并将其返回给调用者来实现迭代。

 什么是Python的生成器 Generator(通俗讲)

  生成器就是一种特别的迭代器(iterator)。

  生成器的大部分使用方法是跟迭代器一样的
  生成器代码更简洁, 而且具有临时修改当前元素的功能

在Python中,生成器是一种特殊的迭代器,它允许你按需生成值,而不是一次性生成所有值。这使得生成器非常适合处理大数据集或无限序列。

生成器使用了yield语句,将返回值给调用者,而不是通过return语句。它允许函数在每次调用时产生一个值,并在下一次调用时从上次停止的地方继续执行。这样的机制避免了一次性加载所有数据到内存中,从而提高了效率。

以下为一个简单的生成器代码示例:

def generator():
    yield 1
    yield 2
    yield 3
 
g = generator()
print(next(g))  # 输出 1
print(next(g))  # 输出 2
print(next(g))  # 输出 3
我们通过Python内置的 next() 方法调用生成器的每一次生成值,一直到取值完成。

注意:当生成器中无值可迭代时,再使用 next() 则会报异常。

为什么要使用Python生成器
1、节省内存:生成器按需生成值,避免了一次性加载所有数据到内存中。这对于处理大型数据集尤其重要。

2、惰性计算:生成器支持惰性计算,只有在需要时才计算值。这在处理无限序列或需要动态生成数据的场景中非常有用。

3、代码简洁:生成器使代码更加清晰、简洁,减少了样板代码的使用。

使用场景
生成器在以下情况下特别有用:

1、大数据集处理(数据流处理):当处理大型数据集时,使用生成器可以避免内存溢出问题。比如可以处理大量数据,如日志文件、网络数据流等,避免一次性加载到内存中。

def file_reader(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            # 在这里可以对每行数据进行处理
            processed_line = line.strip()  # 去除行尾的换行符等
            yield processed_line
 
# 使用示例
file_path = 'large_file.txt'
line_generator = file_reader(file_path)
 
for line in line_generator:
    print(line)
2、无限序列:生成器可用于表示无限序列,例如斐波那契数列。

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b
 
for num in fibonacci(10):
    print(num)
3、惰性计算:当需要按需计算值时,生成器可以提供灵活的解决方案。例如斐波那契数列也是一种惰性计算类型。

4、当然并发编程也可以借助生成器来实现,生成器可以简化协程的实现,提高程序的并发性能。


如何使用Python生成器
使用生成器非常简单。只需定义一个包含yield语句的函数,然后在需要的时候调用它。以下是一个简单的例子:

def countdown(n):
    while n > 0:
        yield n
        n -= 1
 
# 使用生成器
for i in countdown(5):
    print(i)
这个例子中,countdown生成器每次被调用时产生一个递减的数字,直到达到0为止。

生成器的另一个常见用途是为表示值集合(例如列表或字典)的对象实现自定义迭代器。这也就需要说到再Python中另一种生成器使用形式:列表生成器。

在Python中,列表生成式(List Comprehensions)是一种简洁的方式来创建列表。它可以在一行代码中通过对序列进行迭代和应用条件来生成新的列表。下面是一个简单的示例,演示了如何使用列表生成式:

# 创建一个包含1到10的平方的列表
squared_numbers = [x**2 for x in range(1, 11)]
print(squared_numbers)
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。

只要把一个列表生成式的[]改成(),就创建了一个generator:

# 创建一个包含1到10的平方的生成器
squared_numbers = (x**2 for x in range(1, 11))
print(squared_numbers)

注意事项
在使用生成器时,有一些注意事项需要考虑:

1、一次性使用:生成器通常是一次性的,一旦遍历完毕,就需要重新创建生成器对象。

2、yield表达式:确保在生成器函数中正确使用yield语句,以便在每次调用时正确产生值。

3、迭代器协议:生成器必须遵循迭代器协议,即实现iter()和next()方法。

只要一个函数function中使用了 yield 这个关键字,就代表这个函数function每次调用时返回的是一个生成器对象 generator object。这个生成器对象的类型是<class ‘generator’>。

包含 yield 语句的函数function本身并不是生成器generator,它仍然是一个函数function。生成器generator是一个类class,而不是函数function。

生成器generator是迭代器Iterator的一个子类subclass。

生成器generator保存的是产生item的生成方法/算法,而不是items。

next() 函数只能用于生成器generator类型。不能用于函数function。

def func():
yield "Hello"


print(func) # <function func at 0x10d55c0d0>
print(type(func)) # <class 'function'>

g1 = func()
g2 = func()
print(id(g1), id(g2)) # 4519738272 4519739168
print(g1) # <generator object func at 0x10d65bba0>
print(type(g1)) # <class 'generator'>
print(next(g1)) # Hello

为什么 Python 有生成器Generator?
我们可以通过在 Python 类class中实现implementing __iter__() 和 __next__() 特殊方法special methods来获得迭代器Iterator。不过,这种方法有点复杂,尽管它有助于理解迭代器Iterators的真正工作原理。

通过生成器generators创建迭代器Iterators是一种更好、更方便的方法。事实上,生成器就是迭代器的子类the Generator is a subclass of the Iterator。

Iterable可迭代对象、Iterator迭代器 和 Generator生成器 的关系如下:

 

如上图所示,Iterator 是 Iterable 的子类,Generator 是 Iterator 的子类。

# 源码在_collections_abc.py
class Iterable(metaclass=ABCMeta):
@abstractmethod
def __iter__(self): ...
1

# 源码在_collections_abc.py
class Iterator(Iterable):
@abstractmethod
def __next__(self):
raise StopIteration
def __iter__(self):
return self

# 源码在_collections_abc.py
class Generator(Iterator):
def __iter__(self):
return self
def __next__(self):
"""Return the next item from the generator.
When exhausted, raise StopIteration.
"""
return self.send(None)
@abstractmethod
def send(self, value):
"""Send a value into the generator.
Return next yielded value or raise StopIteration.
"""
raise StopIteration
@abstractmethod
def throw(self, typ, val=None, tb=None):
"""Raise an exception in the generator.
Return next yielded value or raise StopIteration.
"""
...
def close(self):
"""Raise GeneratorExit inside generator.
"""
.生成器(Generator)与迭代器(Iterator)具有相同的作用,用于保存一个知道如何生成所需元素的方法method。在Python中操作一个大的列表是非常耗时的。如果我们每次只需要获取一个元素element,那么生成器generator就是一个很好的选择,它可以减少时间和空间成本。

在 Python 中,只要一个函数function中使用了 yield 这个关键字,就代表这个函数function每次调用时都是返回一个生成器对象 generator object,注意:包含 yield 语句的函数function本身并不是生成器generator,它仍然是一个函数function。生成器generator是一个类class,而不是函数function。而 yield 的作用就相当于让 Python 帮我们把一个“串行”的逻辑转换成 iterator 的形式。

生成器generator都是Iterator迭代器对象。

如何获得生成器Generator?
1. 生成器表达式 Generator Expression
生成器表达式generator expression是获取生成器generator的最简单方法。它与 列表推导式list comprehensions 非常相似。我们只需将括号brackets改为小括号parentheses。

my_list = [i for i in range(8)]
my_generator = (i for i in range(8))

print(my_list)
print(my_generator)

# [0, 1, 2, 3, 4, 5, 6, 7]
# <generator object <genexpr> at 0x7f8fc3ec9a40>

由于生成器generator保存的是item生成方法而不是items,因此我们需要使用 next() 函数逐个获取项目get items one by one,这与迭代器Iterator相同。当所有项目items都生成后, next() 函数将引发 StopIteration 错误信息。当然,我们也可以使用 for 循环来获取生成器generator中的项目items。

2. 使用yield定义生成器Generator
如果一个函数function包含 yield 语句,它就可以产生生成器generators。

def my_generator(maximum):
n = 0
while n < maximum:
n += 1
yield n
return 'Done'


g = my_generator(maximum=5)
print(g) # <generator object my_generator at 0x10e269ba0>
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # 4
print(next(g)) # 5
next(g)
# Traceback (most recent call last):
# File "/usr/lib/python3.9/code.py", line 15, in <module>
# next(g)
# StopIteration: Done

yield 表示 “产生”或“生成”produce。当程序执行到 yield 语句时,就会 "生产produce"一个值即项目item,而 next() 函数function就会在此暂停pauses there执行,等待下一次调用。

当我们再次使用 next() 函数function对生成器对象generator object进行调用,它会让生成器对象generator object从上一次暂停的位置继续执行,直到遇到下一个 yield 语句或者执行结束。

普通函数normal functions 与 包含 yield 的函数functions including yield 的主要区别在于执行流程execution flow:

普通函数按顺序执行executes sequentially,并在遇到 return 语句statement或到达最后一行final line时返回结果。
包括 yield 的函数会在调用 next() 时执行,并在遇到 yield 语句时返回。再次调用 next() 时,将从上次暂停的 yield 语句处继续执行。
有一个例子:

def example():
print('step 1')
yield 1
print('step 2')
yield 2
print('step 3')
yield 3


g = example()

next(g)
# step 1
# 1
next(g)
# step 2
# 2
next(g)
# step 3
# 3
next(g)
# Traceback (most recent call last):
# File "/usr/lib/python3.9/code.py", line 21, in <module>
# next(g)
# StopIteration

注:包含 yield 语句的函数本身并不是生成器generator。它仍然是一个函数function,但每次调用这个函数function时都可以返回一个生成器对象return a generator,这个生成器对象的类型是<class ‘generator’>。生成器generator是一个类class,而不是函数function。(正如我们之前所说,生成器generator是迭代器Iterator的一个子类subclass)。

next() 只能用于生成器generator类型。不能用于函数function。

def my_generator(maximum):
n = 0
while n < maximum:
yield n
return 'Done'

print(type(my_generator)) # <class 'function'>

print(type(my_generator(5))) # <class 'generator'>

print(my_generator(5)) # <generator object my_generator at 0x10bc42ba0>

print(next(my_generator(5))) # 0

print(next(my_generator))
# Traceback (most recent call last):
# File "/usr/lib/python3.9/code.py", line 15, in <module>
# print(next(my_generator))
# TypeError: 'function' object is not an iterator

更多Generator应用实例
到目前为止,我们知道生成器generators可以帮助我们保存生成项目items的算法,并在需要时生成项目items。与包含所有项目items的庞大列表list相比,生成器可以减少时间和内存成本。

表示无限的数据流infinite stream of data
事实上,生成器generator甚至可以表示无限的数据流infinite stream of data。例如:

def fibonacci():
x, y = 0, 1
while True:
x, y = y, x + y
yield x

fib = fibonacci()
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
# ...
fib 是一个无限生成器infinite generator,我们可以根据自己的需要使用它。

将多个生成器generators组成管道pipeline
生成器generators的另一个有趣应用interesting application是,我们可以将一系列生成器generators组合起来,得到一个新的生成器generator,这在技术technically上被称为 “管道pipeline”。

def times_two(nums):
for n in nums:
yield n * 2

def natural_number(maximum):
x = 0
while x < maximum:
yield x
x += 1

p = times_two(natural_number(10))
print(type(p)) # <class 'generator'>
print(next(p)) # 0
print(next(p)) # 2
print(next(p)) # 4
print(next(p)) # 6
print(next(p)) # 8
print(next(p)) # 10
print(next(p)) # 12
# ...

如上例所示,我们可以使用现有的两个生成器generators来定义一个新的生成器generator。这不是很好吗?

总结

Python生成器是处理迭代任务的强大工具,通过按需生成值,提高了效率,减少了内存消耗。在大数据集处理、无限序列表示和惰性计算方面,生成器都显示出了其优越性。在编写Python代码时,不妨考虑使用生成器来使代码更加优雅和高效。生成器Generator是 Python 中一种非常有用的机制useful mechanism,可以减少时间reduce time和内存开销memory costs。它保存的是产生项item的算法algorithm而不是项items。我们还可以使用生成器generators生成produce无限的数据流infinite data stream和管道pipelines。

 

参考:https://blog.csdn.net/JENREY/article/details/135303885

https://blog.csdn.net/Wang_Jiankun/article/details/84569570

https://blog.csdn.net/nvd11/article/details/138738472

 

posted @ 2024-05-18 15:59  konglingbin  阅读(64)  评论(0编辑  收藏  举报