(小甲鱼python)函数笔记合集七 函数(VIII)总结 生成器的定义、使用和产生生成器的两种方法详解

一、基础复习

  1. 函数的基本用法 创建和调用函数 函数的形参与实参等等
  2. 函数的几种参数 位置参数、关键字参数、默认参数等
  3. 函数的收集参数*args **args 解包参数详解
  4. 函数中参数的作用域 局部作用域 全局作用域 global语句 嵌套函数 nonlocal语句等详解
  5. 函数的闭包(工厂函数)
  6. lambda()函数表达式、map()、filter()函数详解

前言

函数一般运行结束或遇到return语句,意味着函数运行结束,函数中做的所有工作,以及保存在局部变量中的数据也都将会丢失,再次调用这个函数的时候,一切从头再来。有没有办法让函数在退出之后还能保留状态呢?

有没有办法让函数在退出之后还能保留状态呢?

闭包、全局变量和生成器,但过多的使用全局变量会污染命名空间,闭包德定义相对复杂。简单的方法为生成器。

二、生成器的使用

1.生成器的定义与举例
生成器就是在函数中使用yield表达式来代替return语句就可以了。
例1:

>>> def counter():
	i=0
	while i<=5:
		yield i
		i+=1

>>> 
>>> counter()   #得到的不是返回值,而是生成器。
<generator object counter at 0x00000160FDC43CF0>
>>> 
>>> for i in counter():    # 生成器的使用
	print(i)

0
1
2
3
4
5
>>> 

代码解析: for语句它是从一个可迭代对象里面每次获取一个数据,counter生成器它的作用就是每次调用的时候提供一个数据,每次执行到yield的时候,就生成一个数据,暂停并保留状态,下一次调用则从下一个语句i+=1开始继续执行。

生成器不像列表、元祖这些可迭代对象,可以把生成器看作一个制作机器它的作用就是每调用依次提供一个数据,并且会记住当时的状态,而列表元祖这些可迭代对象则是容器,它们里面存放的是早已经准备好的所有数据。生成器其实可以看作是一种特殊的迭代器,其次它支持next()函数。

生成器每调用一次返回一个结果,导致生成器对象是无法使用下标索引,这种随机访问的方式的。

# 对生成器进行赋值
>>> c=counter()
>>> c
<generator object counter at 0x00000160FDCA7750>
>>> next(c)
0
>>> next(c)
1
>>> next(c)
2
>>> next(c)
3
>>> next(c)
4
>>> next(c)
5
>>> next(c)
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    next(c)
StopIteration
>>> 
>>> c[2]    #报错,生成器不支持下标索引的方式
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    c[2]
TypeError: 'generator' object is not subscriptable
>>> 

2.生成器的使用
生成器在斐波那契数列的使用。

斐波那契数列数列由0和1开始,之后的斐波那契数列就是由之前的两数相加得出:0,1,1,2,3,5,8,…

#由于在函数中没有设置结束条件,这样我们就得到了一个永恒的斐波那契数列生成器。
>>> def fib():
	back1,back2=0,1
	while True:
		yield back1
		back1,back2=back2,back1+back2

>>> f=fib()
>>> next(f)
0
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
>>> next(f)
8

使用for语句迭代,而没有在中途设置退出条件的话,那么就会得到一个一直执行下去的数列,直到按ctrl+c退出。

>>> for i in f:
	print(i)

13
21
34
55
89
144
233
377
610
987
1597
Traceback (most recent call last):
  File "<pyshell#39>", line 2, in <module>
    print(i)
KeyboardInterrupt
>>> 

3.生成器表达式
将列表推导式的方括号改成圆括号会得到一个生成器表达式。
列表推导式会一下子将所有的数据生产出来,并放到一个列表中,但生成器就像一只母鸡,一次只下一个蛋。

>>> (i ** 2 for i in range(10))  # 得到一个生成器对象
<generator object <genexpr> at 0x00000160FDCE1570>
>>> t=(i ** 2 for i in range(10))
>>> next(t)
0
>>> next(t)
1
>>> next(t)
4
>>> next(t)
9
>>> next(t)
16
>>> for i in t:
	print(i)

25
36
49
64
81
>>>

4.生成器总结
产生生成器的两种方法:一个就是将普通函数里面的return替换成yield表达式,另一个是直接使用这个生成器表达式。

题目:
1.迭代器可以看作是一种特殊的生成器吗?
答:不可以!
解析:生成器可以看成是一种特殊的迭代器,但反过来说就错了~
2.生成器可以看作是 Python 对于 “延迟执行” 提供的技术支持,这种说法正确吗?
答:正确。
解析:在需要的时候才产生结果,而并非一次性生产,这就是 “延迟执行” 的含义。当然,通过闭包,我们也可以实现类似的效果,不过生成器毕竟是 Python 帮我们定义好的语句,用起来肯定要顺手方便很多。
3. 你觉得生成器通常应用在什么场景会比较合适?
答:生成器应该应用在占用大量存储空间的文件操作上,比方说日志文件通常都特别大,几十上百 G 都算是小弟弟了,如果一次性把它读取到内存中,可想而知结果就是爆内存;另外一个应用场景就是希望输出的结果不断更新的情况,比如生成斐波那契数列,我们希望它每次产生的结果是基于上次的数据进行再运算产生。
4. 请问下面代码会打印 “YES” 吗?

>>> def counter():
...     i = 0
...     while i <= 5:
...         yield i
...         i += 1
...     print("YES")
...
>>> for each in counter():
...     print(each)
...

答:会。
打印结果如下:

>>> for each in counter():
...     print(each)
...        
0
1
2
3
4
5
YES

解析:这个就跟 return 语句不一样了,return 语句一执行,就不会管后面的语句了,yield 语句是 “打断并返回,然后下次执行就从下一条语句继续”。
5.下面这个列表推导式是不是有什么问题?为什么一直不出结果,你有什么办法解决这个问题么?

>>> [x ** 2 for x in range(1000000)]

答:

>>> (x ** 2 for x in range(1000000))

解析:对于百万级运算,生成器(表达式)对比列表推导式的优势就显而易见了;上面列表推导式代码,在多数电脑是要计算很久才能得到结果,因为它是实打实地在计算的;而生成器表达式则是将算法保存起来,但并没有实际去计算,所以就可以做到 “秒回”。不过,生成器表达式也并非没有缺点,它的缺点就是不支持随机访问,也就是下标索引……
6.请将下面的 map() 函数实现改为使用生成器表达式实现。

>>> list(map(abs, (-1, 2, -3, 4, -5)))
[1, 2, 3, 4, 5]

答:

>>> list(abs(x) for x in (-1, 2, -3, 4, -5))
[1, 2, 3, 4, 5]

解析:如果将生成器表达式作为函数的参数,最外层的小括号是可选的。
7.请将下面的生成器表达式实现改为使用 filter() 函数实现。

>>> "".join(x for x in "FishC" if x.isupper())
'FC'

答:

>>> "".join(filter(lambda x : x.isupper(), "FishC"))
'FC'

8.请问下面代码会打印什么内容?

list(map((lambda x : x ** 2), filter((lambda x : x % 2 == 0), range(10))))

答:

>>> list(map((lambda x : x ** 2), filter((lambda x : x % 2 == 0), range(10))))
[0, 4, 16, 36, 64]

解析:
从小括号的匹配结构上来看,不难看出,list() 函数的参数接受的可迭代对象由 map() 函数产生。
map() 函数的第一个参数应该是一个函数对象,这里用了 lambda 表达式,功能是将拿到的数据进行 2 次幂运算;那么第二个参数就是数据的来源,由 filter() 函数提供。
filter() 函数第一个参数也应该是一个函数对象,这里也用了 lambda 表达式,功能是筛选出能被 2 整除的数据;那么第二个参数就是数据的来源,是由 range(10) 获得,后者得到的是 0、1、2、3、4、5、6、7、8、9。其中能被 2 整除的就是 0、2、4、6、8,因此结果就是 [0, 4, 16, 36, 64]。

题目来自小甲鱼函数(VIII)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值