目录
1、闭包
1.1、什么是闭包?
- 在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。
- 闭包也可以用来在一个函数与一组“私有”变量之间创建关联关系。
- 在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
1.2、形成闭包的条件
1、必须要有一个内嵌函数
2、内函数必须引用外函数的变量
3、外函数必须返回内函数(注意这里是返回内函数,而不是内函数的调用)
# 生命周期
def outer():
a = 10
print(f"a is {a}")
outer()
print(a) # 这行代码运行会出现错误
# 这个代码运行会出现错误,是因为外部并没有定义a这个变量。内部使用完a这个变量,就已经把这个变量释放掉了。
def outer(x):
a = 300 # 变量名的解析原则LEGB
def inner():
# x = 90
print(f"两数之和为:{x + a}") # 运行结果为310
print(__name__) #运行结果为__main__
return inner
d = outer(10)
d()
print(dir(d))
print(d.__closure__) # 输出的结果是一个元组,里面存储着变量的内存地址,
# 形成闭包之后,闭包函数会得到一个非空的__closure__属性
1.3、闭包的使用
def outer():
tmp_list = []
def inner(name):
tmp_list.append(1)
print(f"{name} -- {tmp_list}")
return inner
d1 = outer()
d2 = outer()
d1("d1")
d2("d2")
print(id(d1),id(d2))
# 虽然代码一样,但是每次调用外函数都会重新执行,都会创建一个新的tmp_list和inner
print(id(outer()),id(outer())) # 这个是匿名变量
print(id(d1),id(d2))
# python变量分为匿名变量和命名变量
# 匿名变量一般都放到统一的内存空间
# 运行结果
d1 -- [1]
d2 -- [1]
2383138097616 2383138098048
2383426954432 2383426954432
2383138097616 2383138098048
1.4、闭包的结论和好处
结论:
- 元组里面的对象为cell对象,而访问cell对象的cell_contents属性则可以得到闭包变量的当前值(即上次调用之后的值)。
- 随着闭包的继续被调用,变量会再次更新。
- 一旦形成闭包之后,python确实会将__closure__和闭包函数绑定作为储存闭包变量的场所。
好处:
- 闭包不是必须的
- 没了闭包,python的功能一点都不会被影响
- 有了闭包,只是提供给你一种额外的解决办法
2、装饰器
2.1、什么是装饰器?
定义:装饰器是一种程序设计模式,它的本质就是闭包,它在不改变函数或者类的源代码的基础上,添加额外功能。
2.2、装饰器有什么用?
- 可以考虑在装饰器中置入通用功能的代码来降低程序复杂度。
- 引入日志
- 增加计时逻辑来检测性能
- 给函数加入事务的能力
- 权限控制
2.2.1、编写一个装饰器实现权限控制
条件:
- 定义一个全局变量:username
- 定义add函数,实现两个数相加
- 实现login_required装饰器,如果username值为root,提示"欢迎" ,并计算结果,否则"没有权限"
def login_required(func):
def inner(*args,**kwargs):
if username.lower() == "root":
result = func(*args,**kwargs)
print(f"欢迎执行{func.__name__}函数")
return result
else:
return f"执行{func.__name__}没有权限"
return inner
username = input("请输入你的用户名:")
# 函数被修饰符装饰后,返回的都是内函数的值
@login_required # add = login_required(add),相当于执行了这个操作。
def add(a=int(input("请输入加数1:")),b=int(input("请输入加数2:"))):
time.sleep(2)
return f"两数和为:{a+b}"
print(add())
######## 执行结果
请输入你的用户名:root
请输入加数1:2
请输入加数2:3
欢迎执行add函数
两数和为:5
请输入你的用户名:fdjk
请输入加数1:1
请输入加数2:2
执行add没有权限
2.2.2、多个装饰器的使用
可以应用多个装饰器,但是要注意装饰器的执行顺序:从最上面的装饰器的内函数开始执行
# 统计运行时间的装饰器
import time
import functools
def runtime(func):
# 保留传递进来的函数的元数据,将它的元素据赋值给inner。元数据为原来的名字、文档注释等。
@functools.wraps(func)
def inner(*args,**kwargs): # 加上可变长位置参数可以让装饰器更加的通用
start_time = time.time() # 这个是时间戳,从1970年一月一日到至今的总秒数
result = func(*args,**kwargs)
end_time = time.time()
print(f"函数执行花了{end_time-start_time}s")
return result
return inner
def login_required(func):
def inner(*args, **kwargs):
if username == 'root':
print(f"欢迎执行{func.__name__}函数") # 这里要是没有functools.wraps(func),执行结果就会变成"欢迎执行inner函数"
result = func(*args, **kwargs)
return result
else:
return f"执行{func.__name__}权限不够"
return inner
# 限制性login_required的内部函数,执行到func,就跳到runtime的内函数
@login_required # add = login_required(runtime(add))
@runtime # add = runtime(add)
def add( a, b):
return a + b
username = input("请输入您要使用的用户:")
a = int(input("请输入要计算的数字1:"))
b = int(input("请输入要计算的数字2:"))
print(add(a, b))
###### 执行结果
请输入您要使用的用户:root
请输入要计算的数字1:2
请输入要计算的数字2:2
欢迎执行add函数
函数执行花了0.0s
4
2.3、元数据
上面这个练习中使用到了" @functools.wraps(func)",这个东东是用来保留元数据的。
定义:元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息
函数的重要的元信息,比如:名字、文档注释、字符串和参数签名等。
2.3.1、装饰器后为什么元数据会丢失以及怎么保留元数据
为什么会丢失
- 因为return执行的是经过调用封装的函数
怎么保留元数据
- 利用@functools.wraps*(fun),将一个函数的重要内容复制到另一个函数。
温馨提示:任何时候你定义装饰器的时候,都应该使用functools库中的@wraps装饰器来注释底层包装函数。
3、装饰器带参数
原则:
- 自身不传入参数的装饰器,使用两层的函数定义
- 自身传入参数的的装饰器,使用三层的函数定义
3.1、装饰器带参数的使用方法
错误使用方法:
import functools
import time
def runtime(func):
#保留传递进来的函数的元数据,将它的元数据赋值给inner
@functools.wraps(func)
def inner(*args, **kwargs): #让装饰器更加通用
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函数执行花了{end -start}s")
return result
return inner
# 若是两层的带了参数的话,是这么执行的
# a = runtime("name")
# add = a(add)
@runtime("name") # 对于两层的函数,这样写是会报错的。因为func若是为字符串的,func(*args, **kwargs)就没有意义。因为字符串不是一个可调用对象(callable)
def add(a,b):
return a+b
正确使用方法:
import functools
import time
def decp(name):
def runtime(func):
#保留传递进来的函数的元数据,将它的元数据赋值给inner
@functools.wraps(func)
def inner(*args, **kwargs): #让装饰器更加通用
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函数执行花了{end -start}s")
print(f"name is {name}")
print("1")
return result
print("2")
return inner
print("3")
return runtime
@decp(name="sc") # 这个相当于# runtime = deco(name="sc"), func1 = runtime(func1)
def func1():
time.sleep(3)
print("this is func1")
func1()
### 执行结果
3
2
this is func1
函数执行花了8.803873777389526s
name is sc
1
3.1.1、用装饰器带参数的方法重新编写上文中权限控制代码
import time
def test(username):
def login_required(func):
def inner(*args, **kwargs):
if username == 'root':
print(f"欢迎执行{func.__name__}函数")
result = func(*args, **kwargs)
return result
else:
return f"执行{func.__name__}权限不够"
return inner
return login_required
@test(username=input("请输入你的用户名:"))
def add(a=int(input("请输入加数1:")),b=int(input("请输入加数2:"))):
time.sleep(2)
return f"两数和为:{a+b}"
result = add()
print(result)
# 执行结果
请输入你的用户名:root
请输入加数1:2
请输入加数2:3
欢迎执行add函数
两数和为:5
4、用类实现装饰器和装饰器带参数
4.1、用类实现装饰器
class rumtime():
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = time.time()
self.func()
end = time.time()
print(f"用时{end-start}")
@rumtime
def add():
time.sleep(2)
print("day day up")
add()
#### 执行结果
day day up
用时2.001725912094116
4.2、用类实现装饰器带参数
class rumtime():
def __init__(self, name):
self.func = name
def __call__(self, func):
def deco(*args,**kwargs):
start = time.time()
result = func(*args,**kwargs)
end = time.time()
print(f"用时{end-start}")
return result
return deco
@rumtime("name")
def add():
time.sleep(2)
print("day day up")
add()
# 执行结果
day day up
用时2.0109517574310303
5、装饰类
def outer(cls):
def inner(*args,**kwargs):
print(f"class name is:{cls.__name__}")
return cls(*args,**kwargs)
return inner
@outer # A = outer(A)
class A:
def __init__(self, name):
self.name = name
print(type(A))
m = A("sc")
print(m.name)
### 执行结果
<class 'function'>
class name is:A
sc