1、什么是闭包:
在嵌套函数中,内部函数用到了外部函数的变量,则称内部函数为闭包
python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).
形成闭包的条件:
通俗来说:就是定义在某一个函数内部的函数
闭包例子:
# ENV>>> Python 3.6
# NO.1
def line_conf(a, b):
def line(x):
return a * x + b
return line
# NO.2
def line_conf():
a = 1
b = 2
def line(x):
print(a * x + b)
return line
# NO.3
def _line_(a,b):
def line_c(c):
def line(x):
return a*(x**2)+b*x+c
return line
return line_c
闭包的作用: 保存函数的状态信息,使函数的局部变量信息依然可以保存下来
2、装饰器
在说装饰器之前,我们先说⼀个软件设计的原则: 开闭原则, ⼜被成为开放封闭原则。
开放封闭原则是指对扩展代码的功能是开放的,但是对修改源代码是封闭的。这样的软件设计思路可以保证我们更好的开发和维护我们的代码。
python中的装饰器就是对一个函数调用前后增加一些新的代码
装饰器就是在不修改被装饰对象源代码与调用方式的前提下,为被装饰对象添加新功能的工具
不改变调用方式
装饰器本质上是一个函数,他可以让其他函数在不需要做任何代码处理的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper
def foo():
print('i am foo')
foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的是函数对象 wrapper,这条语句相当于
# foo = wrapper
foo() # 执行foo()就相当于执行 wrapper()
use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程
有了 @ ,我们就可以省去foo = use_logging(foo)
这一句了,直接调用 foo() 即可得到想要的结果。你们看到了没有,foo() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
*args、**kwargs
可能有人问,如果我的业务逻辑函数 foo 需要参数怎么办?比如:
def foo(name):
print("i am %s" % name)
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)
。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
类装饰器
没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__
方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
@语法糖:
@ 符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作。
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
叠加装饰器
在同一个被装饰对象中,添加多个装饰器,并执行
@装饰1
@装饰2
@装饰3
def 被装饰对象():
pass
装饰器在调用 被装饰对象时 才会执行添加的功能
叠加装饰器:
- 装饰的顺序:由下到上装饰
- 执行的顺序:由上往下
注意: 无论inner中出现任何判断,最后都要返回“调用后的被装饰对象” func(*args, **kwargs)
需求:为装饰对象添加统计时间 与 登陆认证功能
import time
user_info = {
'urer':None
}
#登陆功能
def login():
username = input("请输入账号:").strip()
password = input("请输入密码:").strip()
with open('user.txt', 'r', encoding='utf-8') as f:
print(line)
name, pwd = line.strip('\n').split(':') # [tank, 123]
if username == name and password == pwd:
print('登录成功!')
user_info['user'] = username
return True
else:
print('登陆失败!')
return False
# 登录认证装饰器
def login_auth(func): # func---》 download_movie
def inner1(*args, **kwargs):
'''
注意: 无论inner中出现任何判断,
最后都要返回“调用后的被装饰对象” func(*args, **kwargs)
'''
# 登录认证
if user_info.get('user'):
res = func(*args, **kwargs)
return res
else:
flag = login()
# 添加用户是否登录判断
if flag:
res = func(*args, **kwargs)
return res
else:
login()
return func(*args, **kwargs)
return inner1
# 统计时间装饰器
def time_record(func):
def inner2(*args, **kwargs):
print('开始统计...')
start_time = time.time()
res = func(*args, **kwargs)
end_time = time.time()
print(f'消耗时间为: {end_time - start_time}')
return res
return inner2
# 下载电影功能
'''
- 叠加装饰器:
- 装饰的顺序: 由下到上装饰
- 执行的顺序: 由上往下
'''
@time_record # inner2 = time_record(inner1地址)
@login_auth # inner1 = login_auth(download_movie)
def download_movie():
print('正在下载电影...')
time.sleep(2)
print('下载电影完成...')
return 'GTWZ.mp4
# login()
# 执行的顺序: 先执行time_record功能,再执行login_auth功能
# 统计登录时间 + 下载时间
# download_movie()
# 装饰顺序
# @login_auth # inner1 = login_auth(inner2)
# @time_record # inner2 = time_record(download_movie)
# def download_movie():
# print('正在下载电影...')
# time.sleep(2)
# print('下载电影完成...')
# return 'GTWZ.mp4'
# 执行顺序:
# 先执行login_auth, 再执行time_record
# 只统计下载电影的时间
# login() # 先调用登录,模拟用户已登录
download_movie()