Python 基础入门第九讲 推导式,迭代器,生成器

本文详细介绍了Python中的推导式,包括列表、字典和集合推导式,以及如何与if条件结合使用。接着讨论了迭代器的概念、判断对象是否为迭代类型和迭代器的方法。此外,还深入探讨了生成器,包括通过yield构建生成器的两种方式,以及send()方法的使用。文章以实例解析了推导式、迭代器和生成器的工作原理和应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第九讲

一. 推导式

1. 列表推导式

列表推导式是一种快速生成列表的方式,其形式我们通过案例来讲一下:
比如我们有一个需求,想要生成一个列表[0.5,1.0,1.5,2.0,2.5…10.0],我们可以这样实现:

li = []
for i in range(1,21):
	li.append(i/2)
print(li)

通过列表推导式,我们可以将for循环写入列表内,则可以这么写:

li = [i/2 for i in range(1,21)]
print(li)

这样就可以更快速,更方便的生成列表,前面是对于i的处理,后面是循环语句。
多讲个例子,比如我们想随机生成一个列表,里面的整数大于-10,小于10:

import random
li = [random.randint(-10,10) for i in range(10)]

random库需要导入,其中.randint(x,y)是在x和y之间随机取一个整数的意思,需要注意的是,这里是可以取到x和y的,for循环在这里只是充当了一个计数器的作用。

再讲一个例子:
现在有一个字典:

dic = {'name':'Tom','age':'18','gender':'male'}

想抽取其中的元素,变成以下形式的列表:

[key:value,key:value]

代码如下:

li = [i+':'+j for i,j in dic.items()]

需要注意一下,dic.items()是返回dict_items([(key,value),(key,value)......])的形式,如果是for i in dic.items(),i代表的是元组(key,value),使用两个参数才会分别代表key和value。

2. 列表推导式和if联用

现在有一个列表:

li = [-7, 2, -2, -3, -5, 10, 5, -1, 10, -8]

我们想要将其中小于0的数字拿出来,平方后组成一个新的数列,可以这么处理:

li = [-7, 2, -2, -3, -5, 10, 5, -1, 10, -8]
li1 = []
for i in li:
	if i < 0:
		i = i**2
		li1.append(i)
print(li1)

如果用列表推导式呢?可以模仿三目运算符的形式:

li = [-7, 2, -2, -3, -5, 10, 5, -1, 10, -8]
li1 = [i**2 for i in li if i < 0]
print(li1)

3. 列表推导式嵌套循环

如果我们单看下面的语句:

li = [i+j for i in '123' for j in 'abc']
print(li)

这个列表里面嵌套了两个for循环,它的运行逻辑并非是将‘123’和‘abc’里面的字符一一对应组合,它相当于这个:

for i in '123':
	for j in 'abc':
		li.append(i+j)
print(li)

每循环到一个i,都会在这个层级上循环一套j,所以输出结果是有九个元素的。

4. 字典推导式

字典推导式采用{}就可以定义。
举个例子,现在有个列表:

li = ['age','name','gender']

现在有个需求,我们想让列表中的元素对应其下标组成一个字典:

{'age':0,'name':1,'gender':2}

这里我们可以这样写:

dic = {i:li.index(i) for i in li}
print(dic)

个人认为实际情况可能用到的不多,可能用map函数会解决实际情况多一点。

5. 集合推导式

大括号除了能用作字典推导式,也可以用作集合推导式,只在细微处有差别。
比如现在有个需求,随机生成10个1-100之间的数字,并且去重:

import random
s = {random.randint(1,100) for i in range(10)}
print(s)

相当于:

import random
s = set()
for i in range(10):
	s.add(random.randint(1,100))
print(s)

6. 没有元组推导式的说法

如果这样写:

tup = (i for i in range(10))
print(tup)

结果是:

<generator object <genexpr> at 0x00000......> # 省略一部分

这是一个generator生成器,之后会讲到。
如果想生成元组,可以这么写:

print(tuple(tup))

这样就可以了。

7. 讲一个有意思的题目

有这样一个式子:

li = [lambda x:x+i for i in range(10)]
print(li[0](10))
print(li[1](10))
print(li[2](10))

大家猜一下打印输出结果为多少?
答案是三个均为19。
接下来需要就这个问题讲一下原理,这里是我借鉴的别人的解释,可以随时复习一下,下面自己解释一遍。
我们必须要知道的是,上面的题目的等价形式是什么:

li = []
for i in range(10):
	def func(x):
		return x+i
	li.append(func)
print(li[0](10))
print(li[1](10))
print(li[2](10))

可一看到,每循环到一个i,都会往列表li里面添加一个函数func,需要注意的是,在定义函数func的局部作用域里并未给i赋值,并且每循环一次只是给列表li里面添加了函数,并未调用函数,所以函数func也不会去外部寻找数值赋给i,在这种情况下,当我们再重新去调用函数的时候,比如这里的li[0](10),函数会从函上一级作用域找数值赋给i,这个时候for循环早已完成,i最后的值是9,所以无论我调用列表里的哪个函数,结果都是一样的。
那如何解决这个问题呢?达到我们想要的目的?
很简单,我们再每一次循环的时候给i赋值即可,这么写:

li = []
for i in range(10):
	def func(x,i=i):
		return x+i
	li.append(func)
print(li[0](10))
print(li[1](10))
print(li[2](10))

如果写成推导式,也就是:

li = [lambda x,i=i:x+i for i in range(10)]
print(li[0](10))
print(li[1](10))
print(li[2](10))

这个时候我们也可以借助闭包的思想,还记得闭包的作用吗?保存局部信息不被销毁,我们可以在函数func外面包一层函数:

li = []
for i in range(10):
	def outer(i):
		def func(x):
			return x+i
		return func
	li.append(outer(i))
print(li[0](10))
print(li[1](10))
print(li[2](10))

只不过这样似乎没法改成推导式了…………

二. 迭代器

1. 迭代和迭代器

迭代:通过for循环遍历对象的每一个元素的过程。
迭代器:是一种可以遍历的对象,里面的元素可以使用next()内置方法一一访问,但是只可以按序访问,不能回溯。

2. 判断对象是否是迭代类型和迭代器

我们可以通过isinstance来判断对象是否是迭代类型和迭代器。
Iterable:可迭代类型
Iterator:迭代器

from collections import Iterable,Iterator
print(isinstance('abc',Iterable))
print(isinstance([1,2,3],Iterable))
print(isinstance(12345,Iterable))
print(isinstance([1,2,3],Iterator))
'''
输出结果依次为:
True
True
False
False

在这里需要说明一下,整型不是可迭代类型,字符串,列表,元组,字典,字节,集合是可迭代类型,但是他们并不是迭代器,迭代器是可以调用next()方法来一一访问的,你可以把迭代器想象成一个压缩包,我们可以通过iter()方法将可迭代对象编程迭代器,这样可以节约内存。

3. _ iter_()和iter()

假设有一个列表li,我们想把它编程迭代器,可以这么写:

li = [1,2,3]
li1 = li.__iter__()
# 或者
li1 = iter(li)  # 这个内置方法比较常用

需要注意的是,这两种方法并不改变原列表的类型,需要用一个新变量来重新接收。

4. _ next _()和next()

当我们已经有一个迭代器的时候,拿上一个例子来说明,可以通过以下方式一一访问:

print(li1.__next__())
print(li1.__next__())
print(li1.__next__())
print(li1.__next__())

或者

print(next(li1))
print(next(li1))
print(next(li1))
print(next(li1))

一次输出只显示一个元素,多个输出按顺序访问元素,输出次数大于元素数量的时候,会显示StopIteration

5. 迭代器不支持索引取值

迭代器不可以像列表那样按照索引取值,会报错。

三. 生成器

1. 什么是生成器?

有时候,序列或集合内元素数量巨大,如果全部制造出来放入内存将对计算机造成巨大压力。如果采用某种算法,需要用到哪个元素就推算出哪个,然后在循环的过程中不断推算出后续的元素,这样就可以节省大量空间,生成器我们称之为generator.

2. 生成器的构建方式(圆括号和yield)

目前学到两种方式:

  1. 圆括号推导式;
  2. yield函数定义

先来讲第一个,第一个在之前其实已经遇到过了,比如下面代码:

g = (i for i in range(10))

这样就已经构成了一个生成器g,生成器和迭代器一样可以用next()方法逐一访问,并且超出范围会抛出异常。

那如何用yield关键字构建生成器?
比如以下代码:

def test(x):
	n = 0
	while n < x:
		yield n 
		n += 1
res = test(10)

如果只构建一个test(x)函数,那么test仍旧是函数,当我们赋予变量x具体值,在函数中运用到关键字yield,并用一个变量res来接收这个函数的调用,那么res就是一个生成器。
yield本身就有“产出”的意思,我们在这里需要拿其和return做一个对比:

  1. 当函数遇到yield关键字时,函数会暂停,并将对象返回出去,下次会继续从上次暂停的地方开始执行;
  2. 当函数遇到return关键字时,函数直接返回该对象。

通过一个其它例子,我们来看更其它场景的过程剖析:

def test():
	print('---1---')
	while True:
		a = yield 2 
		print(a)
res = test()
print(next(res))
print(next(res))

上述代码已经把res变成了一个生成器,它的执行结果为:

---1---
2
None
2

我们需要解释一下这个结果:

  1. 需要注意的是,虽然test函数里面先设置了一个print输出,由于有yield关键字,所以test函数并不会执行,也就是说res = test()命令不会导致---1---输出,当使用next方法时,test函数开始执行,输出---1---后开始进入while循环,遇到yield返回一个2后,程序停止运行,并且程序并没有执行给a的赋值操作,到此为止,是输出结果的前两行是来源于第一个print(next(res))的操作。
  2. 第二个print(next(res))的操作会从上一次next方法结束的地方开始运行,也就是给a赋值的操作,需要注意的是,此时整型2在刚才已经被yield给生成出去了,所以并没有元素留下来可以给a赋值了,因而打印输出a结果为None。在打印输出a之后进入下一次while循环,一直到yield返回整型2程序再次停止。

3. send()方法

采用send方法可以给上一个程序停止的地方(yield返回一个值的地方)输送一个数据,这样不会可以解决数据空白的问题,同时接着往下运行程序。
我们在这里接着上面的例子讲一下生成器里的send()方法:

def test():
	print('---1---')
	while True:
		a = yield 2 
		print(a)
res = test()
print(next(res))
print(res.send(4))

结果为:

---1---
2
4
2

解释一下:
这里需要解释的就是输出的a不再是None,因为我们通过send方法给上一个yield返回值的地方输送了一个整型4,所以给a赋值了4,程序继续往下运行。
强调一下send方法使用既不能放在开头,也不能放在结尾,否则报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值