Python生成器(Generator)的用法(附带实例)
在 Python 中创建一个列表的常用方法是首先定义一个列表变量,然后依次写出列表中的所有元素。
比如,创建一个包含 1~5 的所有整数的列表:
继续扩展,假设创建一个包含 1~10 的所有整数的列表,则使用这种方法就不得不将 10 个整数全部写出来,随着数据量的增大,编程工作量随之骤增。为了减少重复、烦琐的操作,Python 引入 range() 函数,使用 range(1, 11) 函数表示 1~10 的所有整数:
再次改变需求,假设创建一个 1~10 的所有整数平方的列表,则首先想到的是使用 for 循环来实现:
虽然实现了预期的需求,但是需要通过多行代码才能实现,过程非常繁琐。Python 以代码量少、简洁、精炼著称。为了实现需求,Python 提供了高级特性,即列表推导式。
列表推导式的书写规则是,生成的元素放在语句前面,紧跟着的是 for 循环。在列表推导式的循环后加上条件判断,可以创建 1~10 中所有偶数平方的列表:
使用两个 for 循环即可将两个列表中的元素进行全排列,继而生成新的列表:
为了解决这个问题,Python 引入了生成器表达式。
可以通过生成器表达式将列表改为一个生成器。列表一旦被创建,所包含的元素就实实在在地存在内存空间了。列表存放的是元素。生成器存放的是算法。由于通过 next() 调用算法可实时生成元素,因此生成器占用的内存空间很小。
将列表推导式的方括号
为了解决这个问题,借助生成器,将 square() 函数改为生成器函数,使用 yield 关键字:
比如,创建一个包含 1~5 的所有整数的列表:
>>> list = [1,2,3,4,5] >>> list [1, 2, 3, 4, 5]
继续扩展,假设创建一个包含 1~10 的所有整数的列表,则使用这种方法就不得不将 10 个整数全部写出来,随着数据量的增大,编程工作量随之骤增。为了减少重复、烦琐的操作,Python 引入 range() 函数,使用 range(1, 11) 函数表示 1~10 的所有整数:
>>> list = range(1, 11) >>> list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
再次改变需求,假设创建一个 1~10 的所有整数平方的列表,则首先想到的是使用 for 循环来实现:
>>> list = [] >>> for num in range(1, 11): ... list.append(num * num) ... >>> list [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]在程序中,首先定义了一个空列表 list,然后通过 for 循环遍历 1~10 的所有整数,将 10 个整数进行平方运算之后,使用 append() 函数添加到列表中。
虽然实现了预期的需求,但是需要通过多行代码才能实现,过程非常繁琐。Python 以代码量少、简洁、精炼著称。为了实现需求,Python 提供了高级特性,即列表推导式。
Python列表推导式
列表推导式(list comprehensions)是 Python 内置的、简单且强大、可以用来轻松创建列表(list)的方法,使用非常简单的语句利用其他列表创建新的列表,使用一行语句即可创建 1~10 的所有整数平方的列表:>>> list = [num * num for num in range(1, 11)] >>> list [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
列表推导式的书写规则是,生成的元素放在语句前面,紧跟着的是 for 循环。在列表推导式的循环后加上条件判断,可以创建 1~10 中所有偶数平方的列表:
>>> list = [num * num for num in range(1, 11) if num % 2 == 0] >>> list [4, 16, 36, 64, 100]
使用两个 for 循环即可将两个列表中的元素进行全排列,继而生成新的列表:
>>> list = [char + num for char in ['a', 'b', 'c'] for num in ['1', '2', '3']] >>> list ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']运用列表推导式可以快速生成列表,且代码非常简洁。
Python生成器表达式
基于列表继续扩展,如果列表中的元素个数持续扩大,达到 10 万个,则如此大的列表将占用巨大的内存空间。假设程序实际上只需要访问列表中的几个元素,那么列表占用的绝大多数内存空间都是多余的,纯属浪费。为了解决这个问题,Python 引入了生成器表达式。
可以通过生成器表达式将列表改为一个生成器。列表一旦被创建,所包含的元素就实实在在地存在内存空间了。列表存放的是元素。生成器存放的是算法。由于通过 next() 调用算法可实时生成元素,因此生成器占用的内存空间很小。
将列表推导式的方括号
[]
改为小括号()
,即可创建一个生成器:
>>> list = [num * num for num in range(1, 6)] >>> list [1, 4, 9, 16, 25] >>> g = (num * num for num in range(1, 6)) >>> g <generator object <genexpr> at 0xb68fa25c> >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>>可以看到,列表 list 一旦被创建,内存空间就存放了所有元素,生成器 g 中的元素将随着 next() 函数的调用实时生成,直到最后没有元素可生成时,抛出 StopIteration 错误。
Python生成器函数
除了生成器表达式可以变为生成器,生成器函数同样可以变为生成器,编写一个函数,通过该函数可以计算任意自然数的平方数。def square(input): list = [] for num in range(input): list.append(num * num) print(list) return list for num in square(5): print(num)运行结果为:
[0] [0, 1] [0, 1, 4] [0, 1, 4, 9] [0, 1, 4, 9, 16] 0 1 4 9 16可以看到,square() 函数在运行过程中会创建一个列表,若计算的自然数变大,列表也会变大,则程序会占用大量的内存空间,当自然数无限大时,程序将因无法分配到足够的内存空间而无法运行。
为了解决这个问题,借助生成器,将 square() 函数改为生成器函数,使用 yield 关键字:
def square(input): for num in range(input): print('before yield') yield num * num print('after yield') for num in square(5): print(num)运行结果为:
before yield 0 after yield before yield 1 after yield before yield 4 after yield before yield 9 after yield before yield 16 after yield可以看到,使用生成器函数同样实现了与普通函数同样的功能,区别如下:
- 生成器代码更简洁:对比两者的代码,除了用于打印提示信息的代码,生成器的代码量更少,结构更简洁。
- 生成器占用内存空间极少:生成器并没有创建列表,更不会因为自然数的变大而消耗大量的内存空间。普通函数则会面临严重的内存空间问题。
- 运行方式不同:普通函数是顺序执行的,直到执行完最后一行语句或遇到 return 语句就返回。生成器函数是遇到 yield 语句就返回,再次执行时,从返回的地方继续执行。