ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

python中的生成器

2022-01-13 20:00:21  阅读:166  来源: 互联网

标签:函数 python 生成器 yield next print my


1 生成器简介

在Python中构建迭代器必须实现一个带有 iter() 和 next() 方法的,跟踪内部状态,并在没有返回值时引发 StopIteration。

Python 生成器是创建迭代器的一种简单方法。

简单地说,生成器是一个函数,它返回一个对象(迭代器),可以迭代(一次一个值)。

2 创建生成器

在Python中创建生成器相当简单。它与定义普通函数一样简单,但使用yield语句而不是return语句

如果一个函数至少包含一条yield语句(它可能包含其他yield或return语句),那么它将成为一个生成器函数。

yield和return都将从函数中返回一些值。不同之处在于,return语句完全终止函数,而yield语句则暂停函数保存其所有状态,然后在后续调用中继续执行。

3 生成器函数和普通函数之间的区别

下面是生成器函数与普通函数的区别:

  1. 生成器函数包含一个或多个 yield 语句;
  2. 调用时,它返回一个对象(迭代器),但不会立即开始执行
  3. iter() 和 next() 这样的方法是自动实现的。因此,可以使用 next() 遍历这些项;
  4. 一旦该函数yield,该函数将暂停,并将控制权转移给调用者;
  5. 局部变量及其状态在连续调用之间会被记住;
  6. 最后,当函数终止时,StopIteration 会在进一步调用时自动引发。

下面是一个例子来说明上面提到的所有要点。有一个名为 my_gen() 的生成器函数,它有几个 yield 语句。

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

运行:

>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()

>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.

>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2

>>> next(a)
This is printed at last
3

>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

在上面的例子中有趣的是,在每次调用之间记住变量n 的值。

这与普通函数不同,当生成函数产生时,局部变量不会被销毁。而且,生成器对象只能迭代一次

重新启动该进程,需要使用 a = my_gen() 之类的东西创建另一个生成器对象。

最后要注意的是,可以直接使用 for循环 的生成器。

这是因为 for循环 接受一个迭代器,并使用 next()函数 对其进行迭代。当 StopIteration 被引发时,它会自动结束。

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n


# Using for loop
for item in my_gen():
    print(item)

输出:

This is printed first
1
This is printed second
2
This is printed at last
3

4 带有循环的生成器

通常,生成器函数是用一个具有适当终止条件的循环来实现的。

看一个生成器的例子,它可以反转一个字符串

def rev_str(my_str):
    length = len(my_str)
    for i in range(length - 1, -1, -1):
        yield my_str[i]


# For loop to reverse the string
for char in rev_str("hello"):
    print(char)

输出:

o
l
l
e
h

在这个例子中,使用了 range()函数 来使用 for循环 逆序获取索引。

注:这个生成器函数不仅可以处理字符串,还可以处理其他类型的可迭代对象,如list、tuple等。

5 生成器表达式

使用生成器表达式可以轻松地动态创建简单的生成器。它使得构建生成器变得容易。

与创建匿名函数的lambda函数类似,生成器表达式也创建匿名生成器函数。

生成器表达式的语法类似于Python中的列表推导式。但是方括号换成了圆括号

列表推导式和生成器表达式之间的主要区别是:

  1. 列表推导式生成整个列表,而生成器表达式每次生成一项。
  2. 生成器表达式具有惰性执行(只在请求时生成项)。

因此,生成器表达式比等效的列表推导式的内存效率高得多。

# Initialize the list
my_list = [1, 3, 6, 10]

# square each term using list comprehension
list_ = [x**2 for x in my_list]

# same thing can be done using a generator expression
# generator expressions are surrounded by parenthesis ()
generator = (x**2 for x in my_list)

print(list_)
print(generator)

输出:

[1, 9, 36, 100]
<generator object <genexpr> at 0x7f5d4eb4bf50>

上面可以看到,生成器表达式并没有立即生成所需的结果。相反,它返回一个生成器对象,该对象只根据需要生成项

下面是如何从生成器中获取项:

# Initialize the list
my_list = [1, 3, 6, 10]

a = (x**2 for x in my_list)

print(next(a))

print(next(a))

print(next(a))

print(next(a))

next(a)

输出:

1
9
36
100
Traceback (most recent call last):
  File "<string>", line 15, in <module>
StopIteration

生成器表达式可以用作函数参数。这样使用时,可以省略圆括号

>>> sum(x**2 for x in my_list)
146

>>> max(x**2 for x in my_list)
100

6 生成器的使用

生成器如此强大的原因:

1. 容易实现

与迭代器类相比,生成器可以以一种清晰和简洁的方式实现。下面是一个使用迭代器类实现幂次为2的序列的示例:

class PowTwo:
    def __init__(self, max=0):
        self.n = 0
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > self.max:
            raise StopIteration

        result = 2 ** self.n
        self.n += 1
        return result

上面的程序冗长且令人困惑。现在,用一个生成器函数来做同样的事情。

def PowTwoGen(max=0):
    n = 0
    while n < max:
        yield 2 ** n
        n += 1

因为生成器可以自动跟踪细节,所以实现更加简洁明了。

2. 节约内存

返回序列的普通函数将在返回结果之前在内存中创建整个序列。如果序列中的条目数量非常大,那么这就有点过分了。

这种序列的生成器实现是内存友好的,是首选的,因为它一次只产生一个项。

3. 代表无限数据流

生成器是代表无限数据流的优秀媒介。无限的数据流不能存储在内存中,而且由于生成器每次只能产生一个项,所以它们可以表示无限的数据流。

下面的生成器函数可以生成所有的偶数(至少在理论上)。

def all_even():
    n = 0
    while True:
        yield n
        n += 2

4. 管道生成器

可以使用多个生成器对一系列操作进行流水线处理。

假设有一个生成器可以生成斐波那契数列中的数字。我们有另一个平方数生成器。

如果想要求出斐波那契数列中数字的平方和,可以通过将生成器函数的输出流水线化来实现。

def fibonacci_numbers(nums):
    x, y = 0, 1
    for _ in range(nums):
        x, y = y, x+y
        yield x

def square(nums):
    for num in nums:
        yield num**2

print(sum(square(fibonacci_numbers(10))))

输出:

4895

这种流水线是高效的,易于阅读的。

参考:programiz

标签:函数,python,生成器,yield,next,print,my
来源: https://blog.csdn.net/mengjizhiyou/article/details/122288373

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有