课堂笔记13(函数闭包、装饰器、推导式、迭代器、生成器)

函数闭包

闭包的定义需要满足的三个条件:

  1. 在一个外函数中定义一个内函数
  2. 内函数里运用了外函数的临时变量
  3. 外函数的返回值 内函数的引用

函数的引用图文解析:
图中t_o这个函数调用赋值的变量就可以称为函数的引用
还有函数调用时候的函数名取掉括号。如图中 tesr_one 也可以称为函数的引用。
在这里插入图片描述

闭包三条件图文解析:
1,外函数中定义内函数
在这里插入图片描述

2、内函数运用了外函数的临时变量
外函数的临时变量也可以称为局部变量
在这里插入图片描述
3、外函数的返回值是内函数的引用
在这里插入图片描述

闭包解析:
在这里插入图片描述
调用test_in:
在这里插入图片描述
在这里插入图片描述
正常调用后执行样式
在这里插入图片描述
验证内函数中返回值是否返回:
在这里插入图片描述
注意:之间用test_in()调用是报错的,因为函数作用域无法直接读取在test函数中的test_in函数。
所以一定要使用第25行代码

总结:
闭包是一种现象
一般情况下,外函数执行完毕时,临时变量会被释放。但此时,外函数发现自己的临时变量(形参或者局部变量)会在将来被使用(在内函数中使用),所以外函数介绍时,在返回内函数引用的同时,将外函数的临时变量与内函数进行绑定。这就是为什么外函数结束,但内函数任可以使用外函数临时变量的原因。

装饰器

@符号+函数名称
同时装饰器要放在所想要绑定的函数上方
在这里插入图片描述

装饰器的文图解析:

思考题:计算test1运行时间:

解题:

第一步:先创建一个test1函数,打印输出–1-- 。要求求得这个输出的运行时间。

def test1():
    print("--1--")


test1()

第二步:导入time内置模块。
time是内置的时间模块
赋值程序起始的时间为start变量。
程序结束的时间为end变量。
程序一共花费的时间就是结束的时间与开始时间的差值。即end-start的值

import time  # python内置时间模块


def test1():
    start = time.time()
    print("--1--")
    end = time.time()
    print(f'spend:{end - start}')


test1()

第三步:运行时间过短,显示的是浮点数0.0
此时加上一个停顿时间(延迟)。即 time.sleep()。

import time  # python内置时间模块


def test1():
    start = time.time()
    time.sleep(2)   # 延迟两秒
    print("--1--")
    end = time.time()
    print(f'spend:{end - start-2}')  # 为了精确度,再减去延迟的两秒


test1()

此处,得到的就是0.0000xxxx秒(一个真实的运算速度)

以上是计算一个函数的运算时间。如果要计算多个函数运算时间,即test2、test3、test4等运算时间。
如果使用复制粘贴的方法,除了要修改函数名和内部对应的名字之外,还会出现重复代码。(下图为例)
在这里插入图片描述

所以,可以来封装函数,专门设置一个调用时间的函数。比如calcu_time

import time  # 时间内置模块,并导入


# 计算时间的函数
def calcu_time():
    start = time.time()
    test1()
    end = time.time()
    print(f'运行时间为:{end - start-2}')


def test1():
    print('--1--')
    time.sleep(2)


def test2():
    print('--2--')
    time.sleep(2)


calcu_time()

这段代码看上去比之前复制粘贴要简洁了,但是也有弊端存在。
可以发现,再calct_time中test1是固定的。如果要调用test2还要在calct_time中进行修改。
在这里插入图片描述
为了更加灵活化 使用传参:

import time  # 时间内置模块,并导入


# 计算时间的函数
def calcu_time(func):
    start = time.time()
    func()
    end = time.time()
    print(f'运行时间为:{end - start-2}')


def test1():
    print('--1--')
    time.sleep(2)


def test2():
    print('--2--')
    time.sleep(2)


calcu_time(test1)
calcu_time(test2)

通过传参的方式调用公用函数
在这里插入图片描述
将一个公共代码提出来进行封装,之后通过传参进行运行。这个方式也存在弊端。
弊端1: 代码要多次调用
在这里插入图片描述

弊端2:不够简洁还有些复杂。
在这里插入图片描述

这时候就需要使用装饰器。

装饰器(@):是一种语法糖,主要用于在函数或者类的基础上添加一些可重复用的额外功能
从应用开发角度来看的话,使用装饰器对应用代码进行功能性扩展和包装,以提高代码的可重复性和可维护性
开发当中尽量遵守:封闭开放原则。
封闭开放:已经实现的代码块尽量不做任何修改,但是又可以在其中拓展新的功能。

装饰器简单来说:在不改变函数源代码的情况下为函数添加新功能

装饰器应用场景:

  • 日志记录
  • 调用跟踪管理及参数验证
  • 在web应用中进行登录验证或路由注册
  • 性能优化中记录任务执行时间

装饰器举例
在这里插入图片描述
在这里插入图片描述
文字步骤:
定义好公共函数print_hw和函数test
之后调用了print_he。在函数中传入了实参test(函数引用)。
即公共函数开始执行,打印输出‘hello world’,打印完毕后将形参f返回出去,此处返回的即是test这个引用。
返回到了该函数的调用处即print_he(test)
用test2来接收该调用。test2是f的本身也就是test。
这样套娃,使在执行test之前先执行了公共函数。
在这里插入图片描述
这样的套娃过程,可以直接用装饰器。
@print_hw
在test函数前给一个装饰器,之后直接调用test函数即可得到效果。
在这里插入图片描述
注意:
函数未调用的时候,装饰器已经执行了。
在这里插入图片描述
装饰器在还没有调用函数的时候已经先调用了print_hw这段函数。
在这里插入图片描述
使用装饰器解决之前的思考题 计算运行test的时间

下图为错误代码:因为重复调用了test1()。
在这里插入图片描述

图文解析:
将装饰器换算成套娃形式。可以看到首先在calcu_time中传入test1。先执行了开始时间,之后调用func()【此处即调用了test1()】那么打印输出了‘- - test1 - -’并停顿两秒,之后执行结束时间。打印运行的时间差。时候返回了func(等于返回了test1),于此同时将这段调用赋值给了test。
然后test()等于再度执行了test1()。
所以重复调用了func()。
在这里插入图片描述
解决问题:用闭包
图文解析:
首先calcu_time(test1)在公共函数中传入test1并不会执行代码,只会返回test_in。拿变量test接收返回值。即 test = test_in
之后调用test() 即调用test_in()函数,那么会执行内函数,先开始时间,之后调用外函数的临时变量(即引用)。此处就是调用test1。func()=test1(),进入test1函数中执行打印程序,之后返回calcu_time函数中执行结束时间,打印两个时间差。
在这里插入图片描述
最后将套娃改成装饰器
在这里插入图片描述

推导式

推导式:python中一种特有的语法,又称为解析式。可以从数据序列构建另一个新的数据序列的结构体。

三种推导式:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式

列表(list)推导式

格式1:[out_express for out_express in input_list] 【单纯循环使用见例1】
方括号+for in 的结构式
out_expres:所要添加到的新列表的内容
for out_express in input_list:for xxx in xxx里面

格式2:[out_express for out_express in input_list if out_express_condition]【需要用到循环判断见例2】
方括号+ for in +if
for out_express in input_list:for xxx in xxx里面。如果满足了 if后面的条件 out_express_condition。
则会将内容放入首位的out_express 中

格式3:[i for row in matrix for I in row]【嵌套,见例3】
第一个for row in matrix 是for循环中的外层循环。
第二个for I in row则是内层循环。【还可以继续往后加for的嵌套】
首位的i:需要达成的样式

格式4:两个循环和两个判断。
[x for row in matrix if row_condition for I in row if i_condition]
在格式2后面再直接➕一个格式2即可

小例子:
生成图中数字组成的列表
在这里插入图片描述
range方法会错误,步长不能为浮点数
在这里插入图片描述
循环控制方法:可以达成但是注意取值范围。
在这里插入图片描述
列表推导式(列表解析式):

li1 = [i/2 for I in range(1,21)]
for I in range(1,21):这段就是数字取值范围
I/2:就是所要添加到新列表的内容
li1变量接收这段代码
打印li1
在这里插入图片描述
例子2:
在这里插入图片描述
for循环样式:for + 判断语句
在这里插入图片描述

方法二:过滤(filter)+ 映射(map)【匿名函数lambda】
在这里插入图片描述

方法三:列表推导式
注意:与三目运算符的区别
在这里插入图片描述

例3:两个for循环的嵌套使用列表推导式

在这里插入图片描述
在这里插入图片描述

字典(dict)推导式

格式:{out_exp_key : out_exp_value for out_exp in input_list}
花括号+键值对+for in

在这里插入图片描述
在这里插入图片描述

集合(set)推导式

格式:{out_exp_res for out_exp in input_set}
例:
在这里插入图片描述
在这里插入图片描述

元组的注意点

如果按推导式使用元组,会发现元组并不是推导式,而是生成器,此时需要强转。
在这里插入图片描述

迭代器

迭代器:是迭代取值的工具,迭代是指一个重复的过程,每一次重复都是基于上一次结果而来迭代提供了一种通用的不依赖索引的迭代取值方式。

可迭代对象:
可以用for循环遍历的对象都是可迭代对象

  • sti(字符串)、list(列表)、tuple(元组)dict(字典)、set(集合)
  • generator(生成器和yield的生成器函数)

判断是否可迭代方法:

方法一:
是否有内置的_iter_方法
例如:字符串sty中
在这里插入图片描述
再例如列表list中
在这里插入图片描述
在 整数 int、float 中就不存在

方法二:
isinstance(obj, lterable)
此方法需要导入,从collections中导入 iterable
from callections import iterable
isinstance(‘sdf’,iterable)
在这里插入图片描述

除了以上两种判断可迭代的方式之外,还有迭代器

迭代器:

  • 有内置的_iter_()方法的对象,执行迭代器的_iter_()方法得到的依然是迭代器本身
  • 有内置的_next_()方法的对象,执行该方法可以不依赖索引取值

可迭代对象不一定是迭代器
在这里插入图片描述
调用的时候发现没有next方法
在这里插入图片描述

iter()将可迭代对象转为迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:iterator。可以通过iter()方法将迭代对象转为迭代器。

调用可迭代对象并赋值变量
在这里插入图片描述
还可以用iter()
在这里插入图片描述

next()不通过下标,迭代取值

迭代器不能通过下标去取值,会报错
在这里插入图片描述
next()取值方式:
在这里插入图片描述
已知li的长度是1-4 如果超出长度调用就会报错,提醒停止迭代
在这里插入图片描述
除了_ next _之外就是直接next()
同之前结果一致。打印多次调用,超出长度则会报错停止迭代。
在这里插入图片描述

注意:
  • 超出长度会报错
  • 只能顺延调用,不能逆序调用(即列表顺序是1-4,那么只能1-4,不能反过来从4-1)

可迭代对象与迭代器区别

  • 可以用于for循环的都是可迭代类型(可迭代对象)
  • 作用于next()都是迭代器类型(迭代器对象)
  • list(列表)、dict(字典)、str(字符串)等都是可迭代的但是,不是迭代器,因为next()函数无法调用它们。可以通过iter()函数将它们转为迭代器
  • python的for循环本质就是通过不断调用next()函数实现的

生成器

在python中,一边循环一边计算的机制,称为生成器:generator

生成器存在原因:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, …n]一个列表有从0开始一直到n(这个n不确定数字)那么这个列表就无限大。也意味着存储了无限大的数字,那么占的内存空间就无限大。
在这里插入图片描述
如果列表只要前几集个数,那么后面的数就是浪费的。也就浪费内存。
在这里插入图片描述
生成器就是在循环过程中根据算法不断推算出后续的元素,可以就不用创建整个完整的列表从而节省大量的空间。

生成器创建

方式一:
生成器表达式:
源于迭代列表解析组合,生成器和列表解析式类似,但是它使用的是()而不是[]
(ele_exp【元素表达式】for element in iterable)
在这里插入图片描述
注意: 生成器不可以通过下标取值,会报错
在这里插入图片描述
取值同迭代器一致用next()。同时是顺延执行,不能跳也不能逆序。也不能超出范围,会报错停止迭代。
在这里插入图片描述
超过循环范围:会报错
在这里插入图片描述

方式二:
生成器函数:
当一个函数中包含yield关键字,那么这个函数就不再是一个普通的函数,而是一个generator(生成器)。
调用函数就是创建了一个生成器对象。其工作原理就是通过重复调用next()或者_next_()方法直到捕获一个异常。

send():send()和next()一样,都能让生成器继续往下走(遇到yield返回),但send()能传一个值,这个值作为yield表达式整体的结果。

注意⚠️:yield返回一个值,并且记住这个返回值的位置,下次遇到next()调用时,代码从yield的下一条语句开始执行。与retum的差别是retum也是返回一个值,但是直接结束函数。

下图举例:
实现:生成一个自定义长度的列表
需求:1、定义函数yield_test。2、通过函数参数指定列表长度。
下图函数的弊端:消耗内存大。
在这里插入图片描述
生成器对象:在函数中加入yield,该函数变成了生成器函数
在这里插入图片描述

生成器取值:调用next()

在这里插入图片描述

yield的返回流程:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

和return的区别:
return:函数的返回值,当函数代码执行到retrun的时候退出函数,也就是说retrun下面的代码都不会再执行了。

yield :是将函数变为生成器关键字,将值返回到next(),只不过再遇到下一个next()的时候会接着上一执行的后面继续执行。

send()

在这里插入图片描述
在这里插入图片描述
注意:
1、当如果yield已经返回完毕,再使用next或者send的时候就会报错。【类似超出长度报错】
在这里插入图片描述

2、send使用时候非空的值不能作为启动生成器的对象
在这里插入图片描述
可以使用None ,不过x.send(None)相当于next()
在这里插入图片描述

生成器与迭代器
  • 生成器能做到迭代器能做的所有事
  • 因为生成器自动创建了iter()和next()方法,生成器显得简洁,而且高效

猜你喜欢

转载自blog.csdn.net/yuuisei/article/details/112799801