一.闭包和装饰器
1.闭包
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且**外部函数返回了内部函数****,我们把这个使用外部函数变量的内部函数称为闭包**
"""
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包
闭包的优点:
1.⽆需定义全局变量即可实现通过函数,持续的访问,修改某个值
2.闭包使⽤的变量的所⽤于在函数内,难以被错误的调⽤修改
闭包的缺点:
1.由于内部函数持续引⽤外部函数的值,所以会导致这⼀部分内存空间不被释放,⼀直占⽤内存
"""
# 示例一
def outer(logo):
def inner(msg):
nonlocal logo # 内部函数想要修改外部函数的变量,必须使用nonlocal关键字声明外部函数的变量
info = logo+"Yan"
print(f"<{info}>{msg}<{info}>")
return inner
fn2 = outer("Smartin") # outer函数的返回值是inner函数,将函数名赋值给变量fn1,相当于给inner函数取别名
fn2("测试开发工程师") # 等价于inner("测试开发工程师")
2.装饰器
装饰器是 python 提供的一种语法糖,装饰器使用@符号加上装饰器名称,用于修改其他函数的行为,并且在不修改原始函数定义和调用的情况下,添加额外的功能
装饰器提供了一种简洁而优雅的方式来扩展和修改函数或类的功能.它本质上就是一个闭包函数
装饰器的功能特点:
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
2.1 装饰器的使用
由于装饰器本质上就是一个闭包函数,所以在使用自定义装饰器之前,需要先定义一个用来做为装饰器的闭包
而闭包的外部函数名,就作为装饰器名使用.
import time
def count_time(func):
def inner():
start_time = time.time()
func()
stop_time = time.time()
print(f'函数执行时间为{stop_time - start_time}秒')
return inner
@count_time
def show():
for i in range(3):
print(f"第 {i+1} 次输出")
time.sleep(1)
if __name__ == '__main__':
show()
# 结果:
# 第 1 次输出
# 第 2 次输出
# 第 3 次输出
# 函数执行时间为3.0111730098724365秒
上面代码中,使用闭包实现了一个函数执行时间统计的功能
在 show 函数上,使用闭包函数作为装饰器为 show 统计运行时间
通过代码可以看出,在使用 count_time 函数作为装饰器时,即没有改变 show 函数的内部定义,也没有改变 show 函数的调用方式,但却为 show 函数额外扩展了运行时间统计的功能,这就是装饰器的作用
2.2 装饰器的本质
Python 解释器在遇到装饰器时,会将被装饰函数引用作为参数传递给闭包的外函数,外函数执行后,返回内涵的引用,此时,再将被函数引用赋值给被装饰其函数.
当 Python 解释器执行完装饰过程后,被装饰函数的函数名就不在保存原函数引用,二十保存闭包函数 **inner** 的引用.
而当执行被装饰函数时,实际执行的是闭包函数 inner,由 inner 简介调用被装饰函数,完成整个调用过程
@count_time
def show():
pass
Python 解释器解释过程:
show = count_time(show)
前面的示例代码可修改为:
import time
def count_time(func):
def inner():
start_time = time.time()
func()
stop_time = time.time()
print(f'函数执行时间为{stop_time - start_time}秒')
return inner
def show():
for i in range(3):
print(f"第 {i+1} 次输出")
time.sleep(1)
if __name__ == '__main__':
show = count_time(show)
show()
2.3 通用装饰器
理论上,一个装饰器可以装饰任何函数,但实际前面定义的作为装饰器的 count_time 函数却只能装饰特定的无参无返回值的函数.
如果需要装饰器可以装饰任何函数,那么就需要解决被装饰函数的参数及返回值的问题.
可以通过可变参数和在内函数中返回被装饰函数执行结果的形式解决此问题.
# 做为装饰器名的外函数,使用参数接收被装饰函数的引用
def decorator(func):
# 内函数的可变参数用来接收被装饰函数使用的参数
def inner(*args, **kwargs):
# 装饰器功能代码
# 调用被装饰函数,并将接收的参数传递给被装饰函数,保存被装饰函数执行结果
result = func(*args, **kwargs)
# 返回被装饰函数执行结果
return result
# 返回内函数引用
return inner
2.4 带参数装饰器
除了普通的装饰器使用方式外,在使用装饰器时,还需要向装饰器传递一些参数,比如测试框架 pytest 实现了数据驱动时,可以将测试数据以装饰器参数形式传入,此时,前面定义的作为装饰器的闭包形式就不能满足需求了.
可以在通用装饰器外,再定义一层函数,用来接收装饰器的参数
实现代码
def decorator_args(vars, datas):
def decorator(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
return decorator
data = [(1,2,3),(4,5,6),(7,8,9)]
# 装饰器传参
@decorator_args("a,b,c", data)
def show(a,b,c):
print(a,b,c)
装饰器传参原理
装饰器传参的本质就是链式语法的多次函数调用
@decorator_args(<font style="color:rgb(54, 70, 78);background-color:rgb(245, 245, 245);">"a,b,c", data</font>)解析
- 限制性
decorator_args(<font style="color:rgb(54, 70, 78);background-color:rgb(245, 245, 245);">"a,b,c", data</font>)部分 - 得到结果
decorator与@结合变成装饰器形式@decorator - 通过结果
@decorator装饰器正常装饰被装饰函数
二.生成器
2.1 什么是生成器?
通过列表生成式(列表推导式),我们可以创建一个列表,但是,受到内存限制,列表容量肯定是受限的。而且创建一个包含上百万个元素的列表,不仅占用很大的存储空间,如果我们只需要访问前面几个元素,那么后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推荐出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在python中,这种一边循环一边计算的机制,成为生成器(generator)
2.2 创建生成器
方式一
#创建生成器和通过列表推导式创建列表类似,只需要将[]换为()
#列表推导式
list1 = [x for x in range(10)]
print(list1)
#创建生成器
g = (x for x in range(10))
#打印类型为<class 'generator'>
print(type(g))
#注意生成器直接输出,只能打印出在内存中的地址 <generator object <genexpr> at 0x000000000212E9C8>
print(g)
输入生成器的结果有两种方式
next()
#创建生成器
g = (x for x in range(3))
#输入每次结果
print(g.__next__())
print(g.__next__())
print(g.__next__())
#注意range(3),只能取到0,1,2三个数 所以输出第四次会报StopIteration异常
print(g.__next__())
next()
#创建生成器
g = (x for x in range(3))
#使用next(生成式对象)输入每次结果
print(next(g))
print(next(g))
print(next(g))
#注意range(3),只能取到0,1,2三个数 所以输出第四次会报StopIteration异常
print(next(g))
方式二:使用函数创建
def fun1():
n = 0
while True:
n += 1
#注意yield的作用是return + 停止
yield n
g = fun1()
#注意输入同样可以使用next()或者__next__()方式
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
三.迭代器
3.1 什么是迭代器?
迭代器是访问集合元素的一种方式.迭代器是一个可以记住遍历位置的对象.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退
可以被 next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
3.2 Python 中常见的可迭代对象有哪些?
3.2.1 常见的可迭代对象:
- 生成器
- 列表,元组,字典,字符串
3.2.2 如果返回一个对象是否可迭代呢?
#判断是否为可迭代对象
from collections.abc import Iterable
list1 = [1,3,5,7,9]
#判断列表是否可迭代,返回的是True
a = isinstance(list1, Iterable)
print(a)
#判断字符串是否是可迭代对象,返回的是True
a = isinstance("Augus", Iterable)
print(a)
#判断 整型 是否是可迭代对象,返回的结果是False
a = isinstance(1234, Iterable)
print(a)
注意:
- 生成器就是一个迭代器
- 可迭代的对象不一定就是迭代器
上面我们提到的,列表,元组,字典,字符串等虽然是一个可迭代的对象,但不是一个迭代器,最简单的判断方式就是看是否可以使用 next()方式取值,如下面代码:可迭代的对象列表,元组,字典,字符串等本身不是迭代器,但是可以转换为迭代器
list1 = [1,3,5,7,9]
#列表使用next取值,直接会抛出异常:TypeError: 'list' object is not an iterator
print(next(list1))
print(next(list1))
print(next(list1))
3.3 如何将可迭代对象列表,元组,字典,字符串等转换为迭代器?
将可迭代对象转换为迭代器需要借助与 iter()进行转换,代码如下:
#定义列表
list1 = [1,3,5,7,9]
#使用iter方法 将列表转换为 迭代器
iter_list1 = iter(list1)
#输出迭代器iter_list1的值
print(next(iter_list1)) #1
print(next(iter_list1)) #3
print(next(iter_list1)) #5
Python闭包、装饰器、生成器与迭代器详解

112

被折叠的 条评论
为什么被折叠?



