列表生成式、和生成器、迭代器与可迭代对象
看py代码的时候,每次遇到类似列表生成式与生成器(generator)相关内容的时候,都需要去百度,一次两次三次,每次都不太能记住他们的用法,这里做一个简单总结。
一、列表生成式
按照博大精深的中华文化来理解一下字面意思:它是一个表达式,什么表达式?用来生成列表的一个表达式。
[exp for iter_var in iterable]
嗯,看起来与匿名函数lambda有几分神似。工作过程是这样的:
1、迭代iterable中的每个元素
2、把这些元素依次赋值给iter_var
3、然后通过表达式exp作用于步骤2的每一个值,并生成一个新的元素
4、把步骤3得到的新的元素组成一个新的列表返回
类似于下面这个for循环:
list1 = []
def exp(arg):#这就是exp表达式
return arg**2#表达式的作用是计算入参的2次方
for i in range(5):
list1.append(exp(i))
print(list1)
这一段代码可以用列表生成式实现:
list1 = [x**2 for x in range(5)]
print(list1)
看起来是不是简单很多?当然列表生成式的作用不仅仅是这个。列表生成式还可以带过滤条件:
list1 = [x**2 for x in range(5) if x%2 == 0]#增加了一个if表达式,意思是只计算能被2整除的数的2次方
print(list1)
工作过程在上面的基础上增加了一步:
1、迭代iterable中的每个元素(此处为range(5))
2、把这些元素依次赋值给iter_var(x),然后判断if表达式是否成立,成立则进行下一步,不成立则进行下一次迭代
3、然后通过表达式exp(x**2)作用于步骤2的每一个值,并生成一个新的元素
4、把步骤3得到的新的元素组成一个新的列表返回
同样,类似于下面的过程:
list1 = []
def exp(arg):#这就是exp表达式
return arg**2#表达式的作用是计算入参的2次方
for x in range(5):
if x%2 == 0:
list1.append(exp(x))
print(list1)
例子
下面有几个例子,实现方式是“使用列表生成式”和“不适用列表生成式”,看下区别:
eg1:生成一个从3到10的数字列表:
#不使用列表生成式:
list1 = list(range(3,11))
print(list1)
#或
list2 = []
for i in range(3,11):
list2.append(i)
print(list2)
#使用列表生成式
list3 = [x for x in range(3,11)]
print(list3)
eg2:生成一个2n+1的数字列表,n为从3到11的数字
#不使用列表生成式
list4 = []
for n in range(3,11):
list4.append(2*n+1)
#使用列表生成式
list4 = [2*n+1 for n in range(3,11)]
print(list4)
eg3:计算两个集合的全排列,并将结果作为保存至一个新的列表中
#不使用列表生成式
L1 = ['香蕉', '苹果', '橙子']
L2 = ['可乐', '牛奶']
list5 = []
for m in L1:
for n in L2:
list5.append((m,n))
print(list5)
#使用列表生成式
L1 = ['香蕉', '苹果', '橙子']
L2 = ['可乐', '牛奶']
list5 = [(m,n) for m in L1 for n in L2]
print(list5)
eg4:将一个字典转换成由一组元组组成的列表,元组的格式为(key, value)
#不使用列表生成式
D = {'Tom': 15, 'Jerry': 18, 'Peter': 13}
list6 = []
for k,v in D.items():
list6.append((k,v))
print(list6)
#使用列表生成式
D = {'Tom': 15, 'Jerry': 18, 'Peter': 13}
list6 = [(k,v) for k,v in D.items()]
print(list6)
二、map函数和filter函数
map() 会根据提供的函数对指定序列做映射;
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表;
举个例子:
map()
def square(x):
return(x**2)
r = list(map(square,[1,2,3,4,5]))#map函数返回的是一个对象,需要把这个对象转化成列表,然后打印
print(r)#输出[1, 4, 9, 16, 25]
#用匿名函数实现:
L = [1,2,3,4,5]
r = list(map(lambda x:x**2,L))
print(r)
filter()
def is_odd(x):
return(x % 2 == 1)
r = list(filter(is_odd,[1,2,3,4,5]))
print(r)#输出[1, 3, 5]
#用匿名函数实现:
L = [1,2,3,4,5]
r = list(filter(lambda x:x % 2 == 1,L))
print(r)
还有两个稍微难一点的例子:
eg5:把一个列表中所有的字符串转换成小写,非字符串元素原样保留
L = ['TOM', 'Peter', 10, 'Jerry']
# 用列表生成式实现
list1 = [x.lower() if isinstance(x, str) else x for x in L]
# 用map()函数实现
list2 = list(map(lambda x: x.lower() if isinstance(x, str) else x, L))
eg6:把一个列表中所有的字符串转换成小写,非字符串元素移除
L = ['TOM', 'Peter', 10, 'Jerry']
# 用列表生成式实现
list3 = [x.lower() for x in L if isinstance(x, str)]
# 用map()和filter()函数实现
list4 = list(map(lambda x: x.lower(), filter(lambda x: isinstance(x, str), L)))
三、生成器(Generator)
生成器的原理是,按照一种特定的逻辑算法,在调用他时,不断生成后续相应的数据,不调用则不生成。比如生成100万个数字的列表,某种情况下,我们不需要一次全部生成这些数据,而是用到的时候才需要让他生成,这种情况下,如果使用列表生成式或者一次性计算100万个数字的算法的话,就会浪费内存空间,也会使程序的运行时间变长。这个时候可以用到生成器。
生成器的第一种构造方式,可以直接使用列表生成式来构造,只需要把列表生成式的[]变成()就好。比如:
g1 = (2*n + 1 for n in range(3, 6))
也可以使用包含yield关键字的函数来构造(包含yield关键字的函数都叫做生成器),比如
def my_range(start, end):
for n in range(start, end):
yield 2*n + 1
g2 = my_range(3, 6)
可以使用python的type函数,来检验g1、g2是否是生成器
print(type(g1))#输出为:<class 'generator'>
print(type(g2))#输出为:<class 'generator'>
生成器的执行过程:
在函数执行过程中, 遇到yield关键字会中断执行,知道下次调用函数时,在开始从中断的位置开始继续执行。
生成器的常用调用方式:
1、调用next()方法
2、使用循环对生成器对象进行遍历
使用next()方法调用:
print(next(g1))
print(next(g1))
print(next(g1))
print(next(g1))
输出为:
7
9
11
Traceback (most recent call last):
File "***/generator.py", line 26, in <module>
print(next(g1))
StopIteration
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
输出为:
7
9
11
Traceback (most recent call last):
File "***/generator.py", line 31, in <module>
print(next(g2))
StopIteration
当遍历完生成器,再调用next()函数,程序会抛出一个StopIteration的异常
使用循环遍历的方法调用:
for x in g1:
print(x)
for x in g2:
print(x)
上述生成结果时一样的
7
9
11
所以,使用循环遍历的方式,最终不会抛出异常,因为for循环结束的时候,就不会再继续循环了,也就不会再继续调用生成器了,也就不会抛出异常。
最后补充一下关于迭代器(iterator),可迭代对象(iterable),和生成器之间的关系。
1、可以被类似next()这样的函数不断调用,并且不断返回下一个值的对象称为迭代器
2、可迭代对象,类似python基础中学到的列表(list),元组(tuple),字典(dict),字符串(str)都是可以迭代的对象,他们都可以用for循环进行遍历
3、生成器就是一种迭代器
所以,迭代器和生成器一定是可迭代对象,反之不一定。比如list,dict,str等;他们三者也都可以用for循环去循环遍历,但是生成器和迭代器(或许不应该这样说,因为生成器本身就是迭代器的一种)还可以使用next()方法调用并不断返回下一个值。
注:上文引用自云游道士的博客(地址:https://www.cnblogs.com/yyds/p/6281453.html),写的很好,在此借鉴一下。有什么谬误也欢迎指出。
网友评论