Python3基础篇(六)——迭代器与生成器
Python3基础篇(六)——迭代器与生成器,生成式
前言:
阅读这篇文章我能学到什么?
这篇文章将为你介绍Python3的迭代器和生成器以及列表生成式的详细用法。
1 迭代器
在Python3中可以为序列创建迭代器。迭代器是一个用于记录在序列中当前遍历位置的结构,持续访问它将能依次访问序列的所有元素。它从序列的第一个元素开始访问,直到访问完所有元素。迭代器只能前进不能后退,也即访问过的元素不能再次访问。常用的方法有iter()
用于创建迭代器,next()
函数返回当前元素并指向下一个元素。
代码示例:
1 | Tuple = (1, 2, 3) |
运行结果:
1 | 1 |
可以为字符串、元组、列表、集合、字典创建迭代器,也可以把一个类作为迭代器,或者说它们是“可以迭代的”,下面要讲的生成器也是可以迭代的。迭代器也可和for
循环配合使用。
代码示例:
1 | Tuple = (1, 2, 3) |
运行结果:
1 | 1 |
2 生成式
讲生成器之前还需要补充一下Python3中一个很强的特性即“生成式”。在创建列表时,允许将规则写在列表中,Python3将会按照此规则自动推导出列表的元素。这无疑是一种创建列表的简洁写法。
2.1 为什么需要“生成式”?
假设让你创建这样一个列表,他的元素符合公式$y=x^2+x$,x为自然数。如果告诉你只需要前五项,你可以很轻松的创建出这个列表
1 | List = [0, 2, 6, 12, 20] |
如果我要求写出前10项呢?当然也能写出,不过这次要废一点功夫去计算。
1 | List = [0, 2, 6, 12, 20, 30, 42, 56, 72, 90] |
如果是前100项呢?你可能会先创建一个空列表,然后写一个循环函数去计算没一个元素并将其装入列表中。代码这个样子:
1 | List = [] #创建空列表 |
如果在Python3中这样写将会非常”笨“的写法。可以利用生成式这样创建列表:
1 | List = [x * x + x for x in range(100)] |
我们将公式$x^2+x$直接写在了列表的[]
内,并且使用了for
和range()
函数限定了x的取值范围。这样Python3就可以根据这些规则去自动推导出列表中的每一个元素。你可以将[]
内的代码分成两部分来看,一部分是生成规则x * x + x
,另一部分是循环结构的范围限定for x in range(100)
。
列表生成式主要作用就是使得创建列表变得更简便。
再思考一个问题,如果要求创建的列表元素包含前一千,甚至一万项呢?当元素个数庞大的时候,会带来另外一个问题,就是内存开销过大。这时候我们可以牺牲一点时间效率去节省空间效率。让列表只记录计算规则(公式),当需要访问元素时实时计算生成,不需要保存所有元素,这就是后面要讲到的生成器的作用。
2.2 列表生成式语法规则
活用生成式将会使得代码更简洁有效。
2.2.1 生成式给出规则和范围
上面我们已经看到了列表生成式中给出生成规则(公式)和参数范围的语法结构了。再回顾一下:List = [x * x + x for x in range(100)]
接下来介绍更复杂一点的用法以满足更多的设计需求。
2.2.2 for语句之后加入if语句
对于上面的列表我们添加一个规则,就是元素既要符合公式$y=x^2+x$,x为自然数,而且要求x必须被能5整除。为了满足这个要求我们需要对元素进行 筛选。使用if语句进行筛选。
代码示例:
1 | List = [x * x + x for x in range(100) if x % 5 == 0] |
运行结果:
1 | [0, 30, 110, 240, 420, 650, 930, 1260, 1640, 2070, 2550, 3080, 3660, 4290, 4970, 5700, 6480, 7310, 8190, 9120] |
筛选后100个元素只剩下20个了。只是在[]
里for
语句之后添加了一句if x % == 0
就对元素进行了筛选,只留下符合条件的元素。
也可以在for语句之后使用多个if
,设置多个筛选条件。
下面的代码筛选条件为能被5和2整除的元素。
代码示例:
1 | List1 = [x * x + x for x in range(100) if x % 5 == 0 if x % 2 == 0] #for语句后面使用两个if,筛选同时满足两个if的条件 |
运行结果:
1 | [0, 110, 420, 930, 1640, 2550, 3660, 4970, 6480, 8190] |
生成式中for
语句之后加入if
语句事先对元素的筛选,需要 ==注意== 的是不能使用if-else
、if-elif
、if-elif-else
这些结构,只能使用if
结构。
2.2.3 for语句之前加入if语句
for语句前面使用if-else
语句,它其实是生成规则(公式)的一部分,你可以简单理解为分段函数。要注意只能使用if-else
结构。
我们对上面的例子我们再增加一个条件,前100个元素中,前50个元素需要满足公式y=x^2+x
,后50个需要满足公式y=-x^2+x
。这是一个分段函数,根据x的值是否大于50去决定使用哪个公式,也即有一个选择的动作,这就需要if-else
去实现。
代码示例:
1 | List = [x * x + x if x < 50 else -(x * x) + x for x in range(100) if x % 5 == 0 and x % 2 == 0] #for后面使用两个if,筛选同时满足两个if的条件 |
运行结果:
1 | [0, 110, 420, 930, 1640, -2450, -3540, -4830, -6320, -8010] |
上面的代码当满足if x % 2 == 0
2.2.4 多个参数
上面我们介绍的生成式都是一个参数即$y=f(x)$的形式,实际上是可以多个参数的,这里我们以两个参数举例。两个参数的生成式需要满足公式$z=f(x,y)$。我们假设需要创建这样一个序列:x参数是奇数,y参数是偶数。计算5以内的x与y的和。需要满足公式$z=x+y$且x∈[1, 3, 5],y∈[0, 2, 4]。
代码示例:
1 | a = [1, 3, 5] |
简单解释下zip()
函数,它是python的内置函数,在这里它的作用合并列表a和b生成列表[(1, 0), (3, 2), (5, 4)]
。x和y的值就取自这个新元组中的元素,从而实现了两个取值不同的参数求和。三个及以上的参数可以类推。
1 | [1, 5, 9] |
2.2.4 多个for
多个for主要用于将多个参数的取值进行一一组合。比如两个字符串"ABC"
和"abc"
它们的字符之间有哪些组合?
1 | str1 = "ABC" |
字符串相加就是把字符串拼接。两个for分别遍历了x和y的所有可能取值,则总的元素可能就是两个for遍历元素的组合。三个及以上个数的for可以类推。
通过上面的学习你可能也惊叹这么复杂的逻辑都可以在列表定义时就极其精简的实现了,这确实是python强大的一个地方。
3 生成器
3.1 使用()创建生成器
迭代器和可迭代的数据类型将所有元素都放在内存,当需要时进行访问读取,如果元素个数庞大将会非常消耗内存。这时候可以牺牲一点时间节省空间,我们使用生成器,它记录的不是每个元素的值,而是生成元素的规则,当进行访问时通过规则实时计算出来。由于生成器也是可迭代的,所以和迭代器一样可以使用next()
、和for
循环进行迭代。
下面代码实现将列表元素用生成器产生,构建了一个生成器。
代码示例:
1 | List1 = [x * x + x for x in range(5)] #使用生成式创建一个列表 |
运行结果:
1 | [0, 2, 6, 12, 20] |
生成器也是可迭代的,所以可使用next()
函数进行迭代,也可使用for
循环进行迭代。也有很多人将生成器看做一种特殊的迭代器。
==生成器和迭代器共同点:==
都能记录元素的当前访问位置,都可被用next()
和for
进行迭代,迭代只能向前不能退后。
==生成器和迭代器不同点:==
在创建上迭代器使用iter()
而生成器使用()
或yield
创建。在迭代的过程中迭代器只是把事先存储的值返回,而生成器需要按照规则去计算后返回。
3.2 使用yield创建生成器
yield被用于函数中调用,使用了该关键字的函数就成了生成器。生成器函数与普通函数不同,它返回的是迭代器。yield
有点类似return
,它们能退出函数并返回一直值。不同的是return不会记录函数的信息,而使用yield会记录下函数当前运行的所有信息后退出函数并返回一个值,待下次再次调用这个函数时,继续使用保存的信息并从上次退出的yield位置开始执行。通俗点说迭代器就是实现了一个“延迟”功能,它可以将函数的执行暂停。包括前面讲的()
创建的迭代器也是同样的思想,不需要一次性将所有结果计算出来保存,而是当需要的时候进行计算并给出结果,最大目的就是为了节省空间。
代码示例:
1 | def IteratorFunction(): |
运行结果:
1 | 1 |
这个专题还没有讲到函数,这里稍微有点“超纲”涉及到函数了。
以上在生成器函数里放置了三个yield,可以理解为三个“断电”。经过多次调用和返回局部变量并没有被释放。生成器函数IteratorFunction
并不能直接用于迭代,而是通过调用它后返回迭代器的特性,创建了TF1
和TF2
两个独立的迭代器,也即能用于迭代的是TF1
和TF2
。反复调用TF1
并不会改变TF2
的运行信息。
放置了三个yield
,调用三次会分别停留在三个yield上,如果调用四次及以上会发生什么?答案是会抛出StopIteration
异常,因为迭代器已经迭代到边界了,也就是执行到return
了,虽然我们省略了它。yield只是暂停return是真正的结束也意味着迭代到达边界。一般我们会在生成器函数里放上循环来使用。
下面这个例子用生成器实现了斐波那契数列的迭代。
1 | def Fibonacci_Iter(): |
运算结果:
1 | 1 |
以上例子为了简单化和说明作用,我将while
循环写成了死循环,也即这个迭代器是没有结束”边界“的,现实计算机不能支持无限次调用的运算量,这是不好的示范,是为了提醒大家不要只关注迭代器迭代推导的规则,还需要关注边界。
重构这段代码:
1 | def Fibonacci_Iter(): |
运行结果:
1 | 1 |
可以看到我们限定了迭代次数为5次,当第六次迭代时由于执行到return
迭代到达边界停止,抛出了异常StopIteration: -1
,其中-1既是我们return的值。