疫情环境下的网络学习笔记 python 3.25

3.25

上节课回顾

  1. 有参装饰器

    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()
    
  2. 让函数名,文档注释也一模一样

    from functiontools import wraps

    @wraps(func)

  3. 可迭代对象:iter()

    迭代器对象:next()

    迭代器结束异常:stopiteration

  4. for循环工作原理

    1. iter()转换成迭代器对象
    2. next(迭代器对象) 得到一个新的值
  5. 惰性计算:只有执行next(),才会往下取一个值,节省内存

  6. 生成器:函数中用yield

    本身不是函数,调用得到的结果与函数体代码无关,得到的结果是一个自定义的迭代器

    def func():
    	print('1')
    	yield 1
    	print('2')
    	yield 2	
    

    yield可以将函数挂起,暂停函数

    return只能返回一次值,函数就结束了,yield可以返回多次值

今日内容

  1. 叠加多个装饰器的加载,运行分析

  2. 生成器的高级玩法:yield 挂起函数

    x = yield 返回值,可以赋值

  3. 三元表达式

  4. 生成式

  5. 函数的递归调用

  6. 二分法

正课

叠加多个装饰器

下面例子,叠加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() 的赋值与返回 的执行顺序

  1. 先g.send() 赋值给左边的x
  2. 执行下面的代码, 到while循环的下一次, 又遇见yield
  3. 这个时候就返回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)
原文地址:https://www.cnblogs.com/telecasterfanclub/p/12567280.html