Python 入门 07 —— 推导式、生成器、解包

一、推导式
推导式其实是循环语句一个简写,以用于快捷地生成列表、元组、字典和集合。例如:

lst1 = [x for x in range(5)]
lst2 = tuple([x for x in range(5)])
lst3 = {x:chr(65+x) for x in range(5)}
lst4 = {x for x in range(5)}
print(lst1,lst2,lst3,lst4)
# [0, 1, 2, 3, 4] (0, 1, 2, 3, 4) {0:‘A’, 1:‘B’, 2:‘C’, 3:‘D’, 4:‘E’} {0, 1, 2, 3, 4}

推导式第一部分是一个运算式,可以有函数,运算的结果就作为序列的一个元素。不断的循环,不断地产生元素,所以元素构成一个序列。

1、推导式的后面部分是一个简写的循环语句,这个循环语句还可以嵌套。例如:

lst1 = [str(x)+’-’+str(y) for x in range(5) for y in range(2)]
lst2 = [str(x)+’-’+str(y) for x in range(5) for y in range(x)]
print(lst1,lst2,sep=’\n’)
# [‘0-0’, ‘0-1’, ‘1-0’, ‘1-1’, ‘2-0’, ‘2-1’, ‘3-0’, ‘3-1’, ‘4-0’, ‘4-1’]
# [‘1-0’, ‘2-0’, ‘2-1’, ‘3-0’, ‘3-1’, ‘3-2’, ‘4-0’, ‘4-1’, ‘4-2’, ‘4-3’]

2、在推导式的最后面还可以加一个if语句来过滤元素。例如:

lst1 = [x**2 for x in range(10) if x>=5]
lst2 = [str(x)+’-’+str(y) for x in range(5) for y in range(x) if x+y>3]
print(lst1,lst2,sep=’\n’)
# [25, 36, 49, 64, 81]
# [‘3-1’, ‘3-2’, ‘4-0’, ‘4-1’, ‘4-2’, ‘4-3’]

二、生成器
生成器从表面上看与推导列一样,也是相当于循环语句的简写,只有在最外面限定只能用小括号,但生成器与推导式有两点本质的不同:
一是生成器最后生成的不是序列,而是一个生成器迭代对象,当然这个生成器迭代对象可以轻易地转换成列表、元组、字典和集合;
二是生成器在编译时仅生成一个生成器对象,只在被调用时才创建生成器迭代对象。生成器对象相当于一台机器,生成器迭代对象相当于产品。

举例:
gen = (x for x in range(5)) # 最外面限定只能用小括号
print(gen) # 产生的是生成器迭代对象 <generator object at 0x00000000024CEDD0>
print(list(gen)) # 调用时会创建生成器迭代对象,生成器迭代对象很容易转换成序列:[0,1,2,3,4]

推导列能用的循环嵌套和最后加一个if语句的方法,在生成器中一样能用。
gen2 = (str(x)+’-’+str(y) for x in range(10) for y in range(x) if x+y>13)
print(list(gen2)) # [‘8-6’, ‘8-7’, ‘9-5’, ‘9-6’, ‘9-7’, ‘9-8’]

以上说的仅是用于生成序列的生成器,因为它的外面用的是小括号,所以可称之为:元组型生成器。另外,当一个函数中有“yield”关键词时,这个函数就变成了一个生成器型函数(又称:函数型生成器)。

很多人将元组型生成器称作“元组生成器”,我认为这不是很妥当,因为这很容易被误认为这个生成器是专门用于生成元组的,而其实不是,它最终生成的是一个生成器迭代对象,这个生成器迭代对象可以被转换成元组,也可以被转换成其它序列。

有 yield 的生成器型函数与无yield 的普通函数相比,区别还是很大的。

1、生成器型函数只有作为next()参数被调用时才会被执行,其它方式无法使用这种函数。

def fun01():
print(’++’); print(’++++’)

f01 = fun01()
f01 # 普通函数正常运行,显示如下:
++
++++

def fun02():
print(’++’); yield 6; print(’++++’)

f02 = fun02()
f02 # 生成器型函数不运行,不显示任何内容
print(f02) # f02 本身只是一个对象:<generator object fun02 at 0x0000000001D9EDD0>

f03 = fun02()
next(f03) # 生成器型函数只有被next()调用时才会运行,显示:++

2、生成器型函数在运行时,遇yield关键词则返回其后的值,然后停止,例如:

f04 = fun02()
print(next(f04)) # 显示如下:
++ # 这是遇yield之前的print(’++’)语句显示的
6 # 返回yield关键词后面的值,函数运行完yield这一句就停止

3、再次用next()调用生成器型函数时,函数不从第一句开始运行,而是从上一次停止的地方开始运行,即接着上次的继续运行。运行到下一个yield关键词那里又停止。那问题是如果没有了“下一个yield关键词”呢?没有了就抛出异常,一切停止。

f05 = fun02()
print(next(f05)) # 显示如下:
++
6
print(next(f05)) # 显示如下:
++++ # 上一次运行到yield这一句,这一次从这一句之后开始运行
StopIteration # 函数结束前但没有遇见新的yield关键词,则抛出异常

def fun03():
print(’++’); yield 6
print(’++++’); yield 7
print(’++++++’); yield 8
print(’++++++++’)

f06 = fun03()
print(next(f06)) # 显示如下:
++
6
print(next(f06)) # 显示如下:
++++
7
print(next(f06)) # 显示如下:
++++++
8
print(next(f06)) # 显示如下:
++++++++
StopIteration # 函数结束前但没有遇见新的yield关键词,则抛出异常

4、由上可见,生成器型函数用着用着就会抛出异常,解决这个问题方法也很简单,就是将yield关键词放到一个无限循环中,这样就可以无限调用了。
def fun04(x=5):
while True: yield x ; x+=1

f07 = fun04(8)
print(next(f07),next(f07),next(f07),next(f07),next(f07),next(f07)) # 8 9 10 11 12 13

总之,生成器型函数犹如一支牙膏,不挤不出,挤一下出一点,挤完了就抛出异常。

三、解包
解包就是用一个简洁的方式给多个变量赋值。等号左边多个待赋值的变量用逗号“,”分隔,右边一般是列表、元组等序列。例如:
x1,y1,z1 = [16,17,18]
x2,y2,z2 = (26,27,28)

x3,y3,z3 = dict({1:36,2:37,3:38}).items()
x4,y4,z4 = {46,47,48}

print(x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4) # 16 17 18 26 27 28 (1,36) (2,37) (3,38) 48 46 47

在for循环语句中也可以使用解包。例如:
for i,v,n in [(1,2,3),(11,12,13),(21,22,23)]:
print(i,v,n,end=’ ; ') # 1 2 3 ; 11 12 13 ; 21 22 23 ;

发布了8 篇原创文章 · 获赞 0 · 访问量 85

猜你喜欢

转载自blog.csdn.net/Jeff888999/article/details/103933151
今日推荐