生成器

生成器它的本质就是迭代器

首先,我们先看一个很简单的函数:

1
2
3
4
5
6
7
8
def func():
print(11)
return 22
ret = func()
print(ret)
# 运行结果:
11
22

我们只需要修改一个地方就可以把函数变成生成器,就是将函数中的return换成yield就是生成器

定义生成器

1
2
3
4
def func():
print(11)
yield 22
func()

我们这样写没有任何的变化,这是为什么呢? 我们来看看函数名加括号获取到的是什么?

为什么不会执行呢??不是函数名加括号就是调用这个函数吗? 你想的没有问题,只是因为函数体中出现了yield

咱们可以理解为,生成器是基于函数的形式变成的.

我们func()这一步是在创建一个生成器,然后我们就可以赋值到别得变量中

1
2
3
4
5
6
7
def func():
print(11)
yield 22
ret = func()
print(ret)
# 运行结果:
<generator object func at 0x000001A575163888>

获取的这个生成器.如何使用呢???

回想下迭代器是怎么使用的,再想想生成器的本质就是迭代器.我们是不是就可以直接使用迭代器的方式直接使用生成器啦

1
2
3
4
5
6
7
8
9
def func():
print("111")
yield 222
gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器
ret = gener.__next__() # 这个时候函数才会执⾏. yield的作⽤和return⼀样. 也是返回数据
print(ret)
结果:
111
222

那么我们可以看到,yield和return的效果是一样的,但是还是有点区别

  • yield是分段来执行一个函数,yield可以出现多次
  • return是直接停止这个函数,return可以出现多次但是只会执行到第一个就结束了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def func():
print("111")
yield 222
print("333")
yield 444
gener = func()
ret = gener.__next__()
print(ret)
ret2 = gener.__next__()
print(ret2)
ret3 = gener.__next__()
# 最后⼀个yield执⾏完毕. 再次__next__()程序报错
print(ret3)
结果:
111
222
333
444

当程序运行完最后一个yield,那么后面继续运行next()程序会报错

好了生成器我们认识了,生成器有什么作用呢?

生成器的作用

我们来看一下这个需求,公司向楼下好适口的老板订购了10000个包子.好适口老板也实在一下就全部都做出来了  

1
2
3
4
5
6
7
def eat():
lst = []
for i in range(1,10000):
lst.append('包子'+str(i))
return lst
e = eat()
print(e)

这样做是没有问题,但是我们目前这么点人吃不完这么多,只能先放到一个地方,过会在吃的时候包子就凉了.这样也不太好

最后是老板能够咱们吃一个他做一个.这样我们就不用考虑没地方和包子凉的问题了,咱们实现一个吃一个做一个

1
2
3
4
5
6
7
8
9
10
def eat():
for i in range(1,10000):
yield '包子'+str(i)
e = eat()
print(e.__next__())
print(e.__next__())
print(e.__next__())
print(e.__next__())
print(e.__next__())
print(e.__next__())

上下的区别: 第一种是直接把包子都拿来,很占内存也就是很占咱们的位置,第二种使用生成器,想吃就拿一个.吃多少个包多少个.生成器是一个一个的,一直向下进行,不能向上.next()到哪,指针就指到哪儿.下一次继续就获取指针指向的值

对比显示:生成器的好处是节省内存

推导式

列表推导式

列表推导式,生成器表达式以及其他推导式,首先我们先看一下这样的代码,给出一个列表,通过循环,想列表中添加1~10:

1
2
3
4
li = []
for i in range(10):
li.append(i)
print(li)

我们换成列表推导式是什么样的,来看看:

列表推导式的常⽤写法:

1
[结果 for 变量 in 可迭代对象]
1
2
ls = [i for i in range(10)]
print(ls)

列表推导式是通过⼀行来构建你要的列表, 列表推导式看起来代码简单. 但是出现错误之后很难排查.

例. 从python1期到python18期写入列表lst:

1
2
lst = ['python%s' % i for i in range(1,19)]
print(lst)

筛选模式

[结果 for 变量 in 可迭代对象 if 条件]

1
2
lst = [i for i in range(100) if i %2 == 0]
print(lst)

字典生成式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 内置方法初识
l=['a','b','c','d']
for i,v in enumerate(l):
print(i,v)

# 构造字典
keys=['name','age','sex']
vals=['szk',18,'male']
dic={}
for i,k in enumerate(keys):
# print(i,k)
dic[k]=vals[i]
print(dic)

# 字典生成式
dic={k:vals[i] for i,k in enumerate(keys)}
print(dic)
# 也支持if判断
dic={k:vals[i] for i,k in enumerate(keys) if i > 0}
print(dic)

三元表达式

结果 = 条件成立 if 条件 else 条件不成立返回的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 求两个数的较大值
def max2(x, y):
if x > y:
return x
else:
return y

# 三元表达式实现的效果:条件成立的情况下返回一个值,不成立的情况下返回另外一个值
name = input('please input your name>>>:').strip()
res = 'NB' if name = 'szk' else 'SB' # 当name值为szk的时候才会是NB其他情况都是SB
print(res)

"""
语法结构
结果 = 条件成立 if 条件 else 条件不成立返回的值

替换上面max2内的代码
"""

递归函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""
1.什么是函数递归调用?(也可以说成是函数的嵌套调用的一种形式)
在调用一个函数的过程中又直接或者间接的调用该函数本身,称之为函数的递归调用
"""
# 直接调用
def foo():
print('from foo')
foo()
foo()
"""画图诠释递归调用意味着反复的开辟内存空间最后导致内存溢出"""

# ps:补充知识点 查看及修改最大递归深度的方法(不是绝对的精准)
import sys
print(sys.getrecursionlimit()) # 查看最大递归深度 1000
sys.setrecursionlimit(2000) # 修改最大递归深度

# 查看具体的递归深度(直接调用自己)
def foo(n):
print('from foo',n)
foo(n+1)
foo(0)

# 间接调用
def bar():
print('from bar')
func()
def func():
print('from func')
bar()
bar()

# ps:无限的递归没有任何意义的

有意义的递归一定是伴随着n规模的减少而减少
递归的思想是大事化小

举个例子:

1
2
3
1100,求个和?

求n!的阶乘

dWN87F.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

斐波那契数列:1,1,2,3,5,8,13,21........

设f(n)是第n个斐波那契数,

当n<=2,斐波那契数都为1

当n>2,那么第f(n)个斐波那契数就等于前两个斐波那契数之和。


def fib(n):
if n <= 2:
return 1
else:
return fib(n-1)+fib(n-2)

dWtXwD.jpg