闭包函数与装饰器
一、什么是闭包
闭包:闭是封闭(函数内部函数),包是包含(函数内部函数对外部作用域而非全局作用域的变量的引用)。闭包指的是:函数内部函数对外部作用域的引用。
x = 111 # 全局作用域的变量
def outer():
y = 222 # 局部作用域的变量
def inner():
print(x,y) # 引用了全局和局部作用域的变量
return inner
二、给函数体传值的的两种方式
1、传参
def save_file(filename,content): # 通过参数给函数体代码传值
with open(filename,'a',encoding='utf-8') as fw:
fw.write(content)
return
2、闭包
def deco():
x = 1 # x的值写死了,无法修改
y = 10 # y的值写死了,无法修改
def my_max():
if x > y: # 函数内部函数x对外部作用域x=1的引用,就是闭包
return x
return y
return my_max
res = deco()
print(res())
# 闭包函数进阶
def deco(x,y): # x,y的值在调用时可以改变。
def my_max():
if x > y:
return x
return y
return my_max
res = deco(4,9) # 优点一次传参,多次调用。
print(res())
print(res())
三、闭包函数的应用
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,先使用自己外部包裹的作用域。
闭包函数,可以是一次传参,多次调用。
应用领域:延迟计算(原来我们是传参,现在我们是包起来)、爬虫领域。
# 使用函数传参实现
import requests
url = 'https://www.baidu.com'
url2 = '....'
def my_get(url):
response = requests.get(url)
print(response.text)
my_get(url) # 需要时,每次都要定义网址。如果需要爬取的网址比较多,会特别麻烦
my_get(url)
my_get(url)
# 使用闭包函数实现
def deco(url):
def my_get():
response = requests.get(url)
print(response.text)
return my_get
my_jd = deco('http://www.jd.com') # 一次定义,多次调用调用
my_jd() # 多次调用闭包函数
my_jd()
my_jd()
my_baidu = deco('https://www.baidu.com')
my_baidu()
my_baidu()
my_baidu()
my_baidu()
装饰器
什么是装饰器?
器就是一个工具,而程序中的函数就是具备某一功能的工具,所以装饰器就是给被装饰对象添加新的功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加新的功能。
需要注意的是:
- 装饰器本身其实是任意可调用的对象
- 被装饰的对象也是任意可调用的对象
为什么要用装饰器?
如果我们上线了一个项目,我们需要修改某一个方法,但是我们不想修改方法的使用方法,这个时候可以使用装饰器。因为软件的维护必须遵循开封封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能值的是开放的。
装饰器的实现必须遵循两大原则:
1.不修改被装饰对象的源代码
2.不修改被装饰对象的调用方式
怎么用装饰器?
# 提前了解知识
# 导入时间模块
import time
print(time.time()) # 时间戳 当前时间距离1970-1-1 00:00:00相差的秒数
# 1970-1-1 00:00:00是Unix诞生元年
time.sleep(1) # 睡眠3秒,程序暂停3秒
print('FBI warning!')
要实现统计index函数执行的时间
# 源代码
import time
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
# 通过修改源代码实现,实现统计index函数执行的时间
import time
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
start = time.time()
index()
end = time.time()
print(f'index run time {end-start}')
# 通过改变源函数的调用方式实现
import time
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
def get_time(func): # func = index
start = time.time()
func() # index()
end = time.time()
print(f'index run time {end-start}')
get_time(index) # 通过函数传参的方法实现了,但是改变了源函数的调用方式
# 通过闭包函数实现
import time
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
def outter(func): # func = 原始的index
def get_time(): # 无参装饰器
start = time.time()
func()
end = time.time()
print(f'index run time {end-start}')
return get_time
index = outter(index) # get_time
index() # func()= index()
完善装饰器
上述的装饰器,最后调用index()的时候,其实是在调用get_time(),因此如果原始的index()有返回值的时候,get_time()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和get_time()方法的返回值。
import time
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
return 123
def outter(func):
def get_time():
start = time.time()
res = func()
end = time.time()
print(f'index run time {end-start}')
return res
return get_time
index = outter(index) # get_time
res = index # <function outter.<locals>.get_time at 0x079D2DB0>
print(res)
# index()
res2 = index()
print(res2) # 123
澳门最大赌场上线了 性感MM在线发牌
index run time 3.000171661376953
如果我们想要该装饰器取装饰一个有参函数
def login(name):
time.sleep(1)
print(f'{name} is sb')
return 'login'
函数参数的问题:
无参函数和有参函数都可以直接使用,函数可以以接收任意数量的参数。
如果原始的login()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于login = outer(login),实际上outer(login)是等于get_time的,所以,要想login()传参,只需要在get_time()中传参即可。get_time(*args,**kwargs),实际上就是将原始的login(name)中的位置实参用元组接收,关键字实参用字典接收,并赋值给args,kwargs,fun(*args,**kwargs)将赋值的args,kwargs即(元组和字典)打散成位置形参传给login的形参。
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
return 'index'
# res1 = index()
def login(name):
time.sleep(1)
print(f'{name} is sb')
return 'login'
# res = login('egon')
def outter(func):
def get_time(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
end = time.time()
print(f'func run time {end-start}')
return res
return get_time
login = outter(login) # login = get_time
res = login('egon') # login('egon') = get_time('egon')
print(res) # res = 'login'
egon is sb
func run time 1.0000572204589844
login
语法糖
import time
def outter(func):
def get_time(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
end = time.time()
print(f'func run time {end-start}')
return res
return get_time
@outter # 等同于 index = outter(index) # index = get_time
def index():
time.sleep(3)
print('澳门最大赌场上线了 性感MM在线发牌')
return 'index'
# index = outter(index) # index = get_time
# print(index)
index() # get_time() -> 执行index()
@outter
def login(name):
time.sleep(1)
print(f'{name} is sb')
return 'login'
login = outter(login) # login = get_time
print(login)
login('egon') # get_time('egon')——>执行真正的login()
@outter
def home(*args,**kwargs):
time.sleep(1)
return 'home'
home = outter(home) # home = get_time()
print(home)
home() # get_time()——>执行真正的home()
语法糖会将距紧挨着它的可调用对象的名字当做参数自动传入调用outter,自动执行outter函数
@outter
def index()
pass
无参装饰器模板
def outter(func):
def inner(*args,**kwargs):
print('执行被装饰函数之前,你可以做的操作')
res = func(*args,**kwargs)
print('执行被装饰函数之后,你可以做的操作')
return res
return inner
import time
user_auth = {'is_login':None}
def login_deco(func):
def wrapper(*args,**kwargs):
if user_auth['is_login']: # 已经登录了
res = func(*args,**kwargs)
return res
else:
username = input('请输入你的用户名>>>>>>:').strip() # 没有登录,执行登录
pwd = input('请输入你的密码>>>>>:').strip()
if username == 'nick'and pwd =='123':
print('恭喜登录成功')
user_auth['is_login'] = True
res = func(*args,**kwargs)
return res
else:
print('账号密码错误')
return wrapper
@login_deco
def index(name):
time.sleep(0.5)
print(f'{name} is dsb')
return 666
@login_deco
def home():
time.sleep(0.5)
print('home')
return 9999
index('nick')
home()
有参装饰器(最复杂就三层)
def sanceng(data):
def deco(func):
def wrapper(*args,**kwargs):
if data == 'file':
# 执行被装饰器函数之前你可以做的操作
res = func(*args,**kwargs)
# 执行被装饰函数之后你可以做的操作
return res
return wrapper
return deco
装饰器在装饰的时候,顺序从下往上。装饰器在执行的时候,顺序从上往下。