3.25
上节课回顾
-
有参装饰器
def deco(x,y) def outer(func): wrapper(*args,**kwargs): res = func(*args,**kwargs) return res return wrapper return outer @deco(1,2) def index()
-
让函数名,文档注释也一模一样
from functiontools import wraps
@wraps(func)
-
可迭代对象:iter()
迭代器对象:next()
迭代器结束异常:stopiteration
-
for循环工作原理
- iter()转换成迭代器对象
- next(迭代器对象) 得到一个新的值
-
惰性计算:只有执行next(),才会往下取一个值,节省内存
-
生成器:函数中用yield
本身不是函数,调用得到的结果与函数体代码无关,得到的结果是一个自定义的迭代器
def func(): print('1') yield 1 print('2') yield 2
yield可以将函数挂起,暂停函数
return只能返回一次值,函数就结束了,yield可以返回多次值
今日内容
-
叠加多个装饰器的加载,运行分析
-
生成器的高级玩法:yield 挂起函数
x = yield 返回值,可以赋值
-
三元表达式
-
生成式
-
函数的递归调用
-
二分法
正课
叠加多个装饰器
下面例子,叠加3个装饰器
def deco1(x,y)
def outer1(func1): # 拿到wrapper2的内存地址进行装饰,返回wrapper1
def wrapper1(*args,**kwargs):
res1 = func(*args,**kwargs)
return res1
return wrapper1
return outer1
def deco2(x,y)
def outer2(func2): # 拿到的是wrapper3的内存地址进行装饰,返回wrapper2
def wrapper2(*args,**kwargs):
res2 = func(*args,**kwargs)
return res2
return wrapper2
return outer2
def deco3(x,y)
def outer3(func3): #拿到原函数index的内存地址,返回wrapper3
def wrapper3(*args,**kwargs):
res3 = func(*args,**kwargs)
return res3
return wrapper3 # 得到wrapper3的内存地址
return outer3
@deco1(1,2) # deco1(wrapper2)==>index=wrapper1的内存地址
@deco2(1,2) # deco2(wrapper3)==>index=wrapper2的内存地址
@deco3(1,2) #==>outer3==>index=outer3(index)==>index=wrapper3的内存地址
def index():
...
# 最终返回wrapper1的内存地址
在没有调用函数,定义函数的阶段:多个装饰器叠加,加载顺序自下而上
多个装饰器执行顺序
def outter1(func1): #func1=wrapper2的内存地址
...
return wrapper1
def outter2(func2): #func2=wrapper3的内存地址
...
return wrapper2
def outter3(func3): # func3=最原始的那个index的内存地址
...
return wrapper3
@outter1 # outter1(wrapper2的内存地址)======>index=wrapper1的内存地址
@outter2 # outter2(wrapper3的内存地址)======>wrapper2的内存地址
@outter3 # outter3(最原始的那个index的内存地址)===>wrapper3的内存地址
def index(): # 定义阶段自下而上
print('from index')
index() # 调用阶段自上而下
调用函数的阶段:执行顺序:自上而下
yield表达式
g.send( )
除了 next 以外,生成器还有别的用法
def dog(name):
print('ready to go')
while True:
x = yield # x会拿到yiled通过 g.send() 接收到的值
print(name,'x received:', x)
g = dog('aaa') # 此时不会执行,而是返回一个生成器,赋值给g
next(g) # 第一次执行dog(),运行到第一个 yield,挂起函数并返回
g.send('send1') # 使用send方法向dog()中的 yield 赋值'send1',yield赋值给x后,继续往下运行,经过一个while循环又遇到了yield,挂起并返回
g.send('send2') # 向yield赋值'send2',开始执行代码:将yield赋值给x后继续往下执行,遇到下一个yield返回
# ready to go
# aaa x received: send1
# aaa x received: send2
只要函数内出现yield,再调函数就跟原代码没关系了,就会返回一个生成器
g.send(item) 可以给yield赋值,yiled会将得到的值拿给x,相当于在next的基础之上加了一个给yield传值的功能
原来的函数使用方法,函数的执行顺序一溜烟到底,名称空间就被销毁了。使用yield则不会销毁,而是在第一次g.send(None) 后挂起,等待后续传值
其他注意的知识点:
next(g) # 要使用g.send,必须先send None一次或着next一次,让函数在这里挂起,等待传值
g.send('send1')
g.send('send2')
g.close() # 可以关闭生成器,关闭之后不能再send值进去,再传就报错
返回值
def dog(name):
lis=[]
print('ready to go')
while True:
x = yield lis # x会拿到yiled通过 g.send() 接收到的值
print(name,'x received:', x)
lis.append(x)
res = g.send(None)
print(res)
#send给yield传值, 先赋值给左边的x, 执行下面的代码, 到while循环的下一次, 又遇见yield, 这个时候就返回右边的那个返回值
send(),yield() 的赋值与返回 的执行顺序
- 先g.send() 赋值给左边的x
- 执行下面的代码, 到while循环的下一次, 又遇见yield
- 这个时候就返回yield 右边的那个返回值
三元表达式
现有一需求:条件成立,返回一个值,条件不成立,返回另一个值
对这个需求可以写一个函数,if判断,return不同的值
def func(x,y):
if x > y:
return x
else:
reyurn y
res = func(1,2)
使用三元表达式,用一行代码解决上面这个函数的功能
x = 1
y = 2
res = x if x > y else y
# x>y 不成立,返回else 右边的值
# 条件成立时要返回的值 if 条件 else 条件不成立时要返回的值
条件成立时要返回的值 if 条件 else 条件不成立时要返回的值
生成式
列表生成式
把生成列表的代码,改一改放到中括号内
l = ['aaaa','aacca','dddaa','eeeaa']
new_l = [name for name in l if name.endswith('a')]
# for循环每循环一次,做一个条件判断,符合判断的加进列表里
# 相当于
for name in l:
if name.endswith('a'):
new_l.append(name)
也可以不加条件,相当于 if True
new_l = [name for name in l]
print(new_l) # 就是循环原列表一个个加入新列表
课堂小练习:把所有小写字母变成大写,去掉元素的后缀aaa
lis = ['deimsaaa','zjyaaa','bbbaaa','cccaaa','sss']
new_lis = [name[:-3].upper() for name in lis if name.endswith('aaa')]
print(new_lis)
字典生成式
keys = ['name','age','gender']
{key:None for key in keys}
# 生成字典,为所有的key赋值None
items = [('name','deimos'),('age','21'),('gender','mail')]
{for k,v in items if k != 'gender'}
# 循环 items,第一个为key,第二个为值加入字典,判断 k = gender的时候不加入字典
集合生成式
keys = ['name','age','gender']
set1 = {key for key in keys}
print(set1,type(set1))
生成器生成式
除了用yield + 函数定义之外,另一种创建生成器的方法。与列表生成式的语法格式相同,只需要将 []换成 (),返回的不是列表或元组,返回的是一个生成器对象
例子:统计文件中所有字符的个数
-
sum( iteration ):里面放一个可迭代对象,会迭代之后累加
sum的原理也是for 循环,然后next取值进行累加计算
-
生成器生成式:
(expression for item in iterable if condition)
方法一,最原始的方法 用for循环
with open('笔记.txt',mode='rt',encoding='utf-8') as f:
# 方式一:
res=0
for line in f:
res+=len(line)
print(res)
方法二:使用列表生成器,对每一行计算长度 len(line)加入列表,最后求sum
res=sum([len(line) for line in f])
print(res)
# 可以实现,但是当文件有很多行,列表生成器生成的每一个元素都在内存中占地方,sum的运行效率会很低
方法三:使用生成器表达式
res = sum(len(line) for line in f)
print(res)
# 在类似sum() 这样的函数中写生成器表达式不需要加括号,sum() 能够识别
函数递归调用
递归:我 调 我 自 己
def f():
print('f1')
f()
f()
# 直接调用本身
递归像死循环,永远不会结束第一层函数名称空间,python不允许无限地递归,默认设置了最大层级:1000层
def f():
print('f')
f1()
def f1()
print('f1')
f()
f()
# 间接调用,仍然是死循环
递归的本质就是循环,一段代码的循环运行方案有两种:循环,递归,就是用于重复运行代码的
递归无限运行,会无限申请内存空间,python也没有尾递归或别的方法优化,所以不应该无限地递归,必须在满足某种条件下结束递归调用:return
def f(n):
if n == 15: # 判断条件,满足时return退出,不会造成无限递归
return
print(n)
n += 1
f(n)
f(1)
递归的两个阶段
- 回溯:一层层往下调用函数的过程
- 递推:运行结束后一层一层结束函数
函数递归的过程:
回溯====》到达最下一层函数,返回====》一层层递推回来
例子:对一个嵌套了很多层的列表,将列表里列表里列表里所有的元素取出来
items=[[1,2],3,[4,[5,[6,7]]]]
def foo(items):
for i in items:
if isinstance(i,list): #满足未遍历完items以及if判断成立的条件时,一直进行递归调用
foo(i)
else:
print(i,end=' ')
foo(items)