闭包函数与装饰器
闭包函数
闭函数指的是在一个函数内部的函数,即嵌套在函数内部的函数。
包函数指的是内部函数对外层函数(非全局作用域)作用域名字的引用。
闭包函数基于函数对象,可以将函数返回到任意位置调用,但是作用域的关系在定义函数的时候就被确定了,与函数的调用位置无关。如果内嵌函数包含对外部函数作用域中名字的引用,该内嵌函数就是闭包函数。
# -*- coding: utf-8 -*-
def outer_func():
count = 0
def inner_func():
nonlocal count
count += 1
return count
return inner_func
counter = outer_func()
print(counter()) # Output: 1
print(counter()) # Output: 2
在这个例子中,outer_func返回内部函数inner_func,每次调用inner_func时,它会递增count计数器并返回当前计数器的值。由于inner_func与外部函数outer_func的作用域相关联,因此可以保存count的值,即使outer_func已经执行完毕。因此,通过闭包,我们可以在函数之间共享数据和状态信息,这对于编写高效的代码非常有用。
闭包函数的特征
内部函数可以访问外部函数的变量或者参数;
外部函数返回内部函数的引用;
外部函数的变量或者参数在内存中得以保留,供内部函数调用。
在 Python 中,函数是一等公民,意味着函数可以像其他任何对象一样被操作,包括被传递和赋值给变量。这种特性允许变量或参数在内存中得以保留,供内部函数调用。
当外部函数执行结束后,Python 不会立即销毁该函数的局部变量和参数,而是将其保存在与函数名相关联的命名空间中。内部函数在访问这些变量时,实际上是在当前作用域中查找此变量(LEGB规则),如果没有找到,则继续在外层作用域中查找,直到全局作用域。
这种行为称为闭包(Closure),它允许在另一个作用域中捕获外部函数的变量,并通过返回内部函数来保留它们。使用闭包,我们可以将状态和逻辑封装在函数内,可以将代码变得更加简洁和易于理解。
以下是一个简单的示例,说明如何使用函数闭包来保留外部变量:
def outer_function(msg):
def inner_function():
print(msg)
return inner_function
# 调用外部函数,返回内部函数
my_func = outer_function("Hello World!")
# 调用内部函数,输出 "Hello World!"
my_func()
在上面的例子中,外部函数 outer_function
接收一个 msg
参数,并返回内部函数 inner_function
。当我们调用外部函数并将其结果赋值给变量 my_func
时,msg
参数被保存在内存中,并绑定到 my_func
上。每次调用 my_func
时,实际上是在访问 msg
参数,因此我们可以在内部函数中保留和操作外部参数。
onlocal
onlocal
是 Python 3 引入的关键字,用于在嵌套函数中访问和修改上一级函数中定义的局部变量。如果一个变量在当前函数的作用域中没有定义,但是在上一级函数的作用域中有定义,我们可以使用 nonlocal
关键字将该变量标记为非局部变量,这样就可以在当前函数中引用和修改该变量了。
具体地说,nonlocal
关键字的作用相当于 global
关键字,只不过它是访问上一级函数中的变量,而不是全局变量。如果省略了 nonlocal
或者变量在上一级函数中没有定义,则会抛出 SyntaxError
异常。
例如,下面的代码演示了如何在嵌套函数中使用 nonlocal
关键字:
def outer():
x = 10
def inner():
nonlocal x
x += 1
print(x)
inner() # 输出 11
outer()
在上面的代码中,inner()
函数中使用了 nonlocal
关键字来访问和修改外层 outer()
函数中定义的变量 x
,这样就可以在 inner()
函数中访问和修改 x
变量了。
闭包函数作用
函数体传参方式之 形参
def index(username):
print(username)
index('shisbuyu') # 函数体代码需要什么就可以在形参里写什么
函数体传参方式之 闭包
def outter():
name = 'shisbuyu'
def index():
print(name) # name写死了, 调用res() name shisbuyu
return index
res = outer()
res()
做成闭包函数通过形参传参给外层
def outter(name):
def index():
print(name)
return index
res = outter('shisbuyu')
res() # shisbuyu
res1 = outter('shisuiyi')
res1() # shisuiyi
装饰器
装饰器可以在不改动原函数代码的情况下,添加其原本没有的功能。简单点说,就是 修改其它函数的功能的函数 。通过使用装饰器,我们可以让一个函数的功能变的更加强大,还可以让我们的代码更加简短整洁。
装饰器原理阐述:将被装饰的函数当做一个参数传到装饰器中,并且让被装饰的函数名指向装饰器 内部的函数,在装饰器的内部函数再调用被装饰的函数
普通装饰器
方式一:不使用语法糖@符号的调用方法:
# -*- coding: utf-8 -*-
# 原函数
def one():
print("这是一个平平无奇的函数")
# 定义1个装饰器
def loop(func):
def wrapper(*args, **kw):
for i in range(2):
func(*args, **kw)
return wrapper
res=loop(one)
res()
# 输出
这是一个平平无奇的函数
这是一个平平无奇的函数
方式二:使用语法糖@符号的调用方法:
# -*- coding: utf-8 -*-
# 定义1个装饰器
def loop(func):
def wrapper(*args, **kw):
for i in range(2):
func(*args, **kw)
return wrapper
@loop
def one():
print("这是一个平平无奇的函数")
one()
# 输出
这是一个平平无奇的函数
这是一个平平无奇的函数
装饰器装饰类
使用类装饰器的时候,记得要返回被装饰的类调用的结果
def decorator(cls):
def wrapper(*args, **kwargs):
print("----装饰器扩展代码1------")
# 通过类实例化对象
res = cls(*args, **kwargs)
print("----装饰器扩展代码2------")
return res
return wrapper
@decorator # MyClass = decorator(MyClass)
class MyClass:
pass
函数带参数
# -*- coding: utf-8 -*-
def loop(func):
def wrapper(*args, **kw):
for i in range(2):
func(*args, **kw)
return wrapper
@loop
def one(num):
for i in range(num):
print("这是一个平平无奇的函数")
one(2)
#输出
这是一个平平无奇的函数
这是一个平平无奇的函数
这是一个平平无奇的函数
这是一个平平无奇的函数
这段代码定义了一个装饰器函数 loop
,它接受一个函数作为参数,并返回一个新的函数 wrapper
。wrapper
函数会在原始函数之前循环执行两次原函数 func(*args, **kw)
,其中 *args
和 **kw
表示使用任意数目和类型的位置和关键字参数。@loop
语法意味着将函数 one()
包裹在装饰器中,从而每次调用 one()
函数时,实际上是调用了包含两次循环的新函数 wrapper()
。
带返回值
# -*- coding: utf-8 -*-
def loop(func):
def wrapper(*args, **kw):
ret = func(*args, **kw)
return ret
return wrapper
@loop
def one(a, b):
return a + b
res = one(1, 2)
print(res)
# 当使用@loop对one()函数装饰以后,one指向了wrapper()函数,而wrapper()函数的返回值是ret,所以one()函数的返回值被ret接收了
#输出
3
装饰器接受参数
Python 装饰器可以接受参数,这使得装饰器的功能更加灵活和通用。装饰器接受参数的语法比较简单,只需要在装饰器函数外再嵌套一层函数,用于接收装饰器参数并返回一个装饰器函数即可。
具体地说,如果要给装饰器传递参数,可以按照下面的步骤进行:
定义一个函数作为装饰器的参数接收函数,该函数返回一个装饰器函数。
在函数内部定义一个装饰器函数,该函数接收被装饰函数的参数,并根据传入的装饰器参数来实现相应的功能。
将装饰器函数作为返回值返回到装饰器参数接收函数中。
下面是一个简单的例子,演示了如何给装饰器传递参数:
def repeat(num):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def hello(name):
print(f"Hello, {name}!")
hello("world")
最外层参数,接收的是装饰器的参数
第二层参数,接收是被装饰的函数
第三层参数,接收的是被装饰函数的参数
在上面的代码中,我们定义了一个名为 repeat
的函数,它接受一个整数 num
作为参数,并返回一个装饰器函数 decorator
,该函数内部定义了一个装饰器函数 wrapper
,用于实现重复调用被装饰函数的功能。最后,我们把装饰器函数作为返回值返回到装饰器参数接收函数中,并使用 @repeat(3)
的语法将其应用到 hello()
函数上。
运行上面的代码,输出如下:
Hello, world!
Hello, world!
Hello, world!
从输出结果可以看出,我们成功地给装饰器传递了参数,并且实现了重复调用的功能。
装饰器的副作用
问题:函数/类 在被装饰器装饰了之后,会改变原函数名的指向,无法再通过原函数名去获取函数原有的属性
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/5/7 10:39
# @Author : shisuiyi
# @File : 装饰器.py
# @Software: win10 Tensorflow1.13.1 python3.9
from functools import wraps
def decorator1(func):
# 消除装饰器的副作用
# @wraps(func)
def wrapper(a, b):
res = func(a, b)
return res
return wrapper
@decorator1
def work(a, b):
"""
实现两个对象相加的方法
:param a: 数字1
:param b: 数字2
:return: 两个数相加的结果
"""
res = a + b
print('a+b的结果为:', res)
# 获取函数的文档字符串注释
print(work.__doc__)
# 获取函数名
print(work.__name__)
# 输出
None
wrapper
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/5/7 10:39
# @Author : shisuiyi
# @File : 装饰器.py
# @Software: win10 Tensorflow1.13.1 python3.9
from functools import wraps
def decorator1(func):
# 消除装饰器的副作用
@wraps(func)
def wrapper(a, b):
res = func(a, b)
return res
return wrapper
@decorator1
def work(a, b):
"""
实现两个对象相加的方法
:param a: 数字1
:param b: 数字2
:return: 两个数相加的结果
"""
res = a + b
print('a+b的结果为:', res)
# 获取函数的文档字符串注释
print(work.__doc__)
# 获取函数名
print(work.__name__)
# 输出
实现两个对象相加的方法
:param a: 数字1
:param b: 数字2
:return: 两个数相加的结果
work
消除装饰器的副作用:functools.wraps
函数的几个特殊的属性
获取函数名:func.__name__
获取函数的文档字符串注释: func.__doc__
@wraps(func) 用于修复被装饰之后的函数的函数名和文档字符串注释信息
多重装饰器
def dec1(func):
def wrapper(*args, **kwargs):
print("Calling dec1")
return func(*args, **kwargs)
return wrapper
def dec2(param):
def decorator(func):
def wrapper(*args, **kwargs):
print("Calling dec2 with param", param)
return func(*args, **kwargs)
return wrapper
return decorator
def dec3(func):
def wrapper(*args, **kwargs):
print("Calling dec3")
return func(*args, **kwargs)
return wrapper
@dec1
@dec2("hello")
@dec3
def my_func():
print("Hello, world!")
my_func()
# 输出
Calling dec1
Calling dec2 with param hello
Calling dec3
Hello, world!
Process finished with exit code 0
首先,最后一个装饰器 dec3
会先被调用,然后将 my_func()
作为参数传递给它,并返回一个新的函数。接下来,dec2("hello")
装饰器会被调用,将 dec3
返回的新函数作为参数传递给它,并返回一个新的函数。最后,dec1
装饰器会被调用,将 dec2
返回的新函数作为参数传递给它,并返回一个新的函数。所以,最终的 my_func()
函数是由 dec1
返回的新函数构成的。
因此,在执行 my_func()
函数时,会先调用 dec1
的包装函数 wrapper
,然后调用 dec2
的包装函数 wrapper
,最后调用 dec3
的包装函数 wrapper
。每次包装函数的执行都会在终端上打印出相应的信息,最后才会打印出 "Hello, world!"。
实际案例
日志装饰器
# -*- coding: utf-8 -*-
import logging
import os
'''
* %(asctime)s 即日志记录时间,精确到毫秒@breif:
* %(levelname)s 即此条日志级别@param[in]:
* %(filename)s 即触发日志记录的python文件名@retval:
* %(funcName)s 即触发日志记录的函数名
* %(lineno)s 即触发日志记录代码的行号
* %(message)s 即这项调用中的参数
'''
if not os.path.exists('Log.log'):
file = open('Log.log', 'w')
logging.basicConfig(
filename='Log.log',
format="%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s"
)
'''
* @breif: 日志修饰器,为函数添加日志记录服务
* @param[in]: err -> 发生异常时返回的错误信息
* @retval: 加载日志服务的功能函数
'''
def logger(err):
def log(func):
def warp(*args, **kwargs):
try:
result = func(*args, **kwargs)
return result
except Exception as e:
logging.error(e)
return err
return warp
return log
@logger('出错了')
def add(a, b):
return a + b
print(add(1, 2))
#控制台输出
出错了
# Log.log日志显示
2022-12-13 14:11:22,772 - ERROR - 闭包函数.py - warp - 36 - unsupported operand type(s) for +: 'int' and 'str'
自动化测试脚本中处理异常
举个例子,你正在跑一个自动化测试脚本,突然间设备的闹钟响了,而刚好闹钟的界面遮挡住了你要操作的那个按钮,最终导致脚本运行失败了。没办法,你只能关掉闹钟再重新运行一次脚本。
# -*- coding: utf-8 -*-
def test_retry(func):
def run_case_again(*args, **kwargs):
try:
func(*args, **kwargs)
except:
# 如闹铃、断网重试按钮弹出、短信通知等导致失败
if exists(xxxx):
touch(xxx)
try:
func(*args, **kwargs)
print('用例重新执行成功')
except:
print('用例执行失败')
# 其他你想做的操作,比如停止剩余用例的执行
return run_case_again
@test_retry
def test():
wait(xxxx, timeout=15)
实现一个可以统计任意函数执行时间的装饰器
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/5/7 10:39
# @Author : shisuiyi
# @File : 装饰器.py
# @Software: win10 Tensorflow1.13.1 python3.9
import time
def count_time(func):
def wrapper():
strat_time = time.time() # 获取开始时的时间
func() # 运行被装饰的函数
end_time = time.time() # 获取结束时的时间
t_time = end_time - strat_time # 算出运行总时间
print('运行总时间%:', t_time) # 打印运行总时间
return wrapper
@count_time
def work():
time.sleep(2)
print('原来函数的功能代码')
if __name__ == '__main__':
work()
评论