之前总是为了学习而去学习,所以对一个知识点总是知其然而不知其所以然,如果在认识一个新的知识点的过程中没有疑问,那一定是没有掌握这个东西,甚至可以说是没有入门。这样的结果一定是似懂非懂,最后慢慢遗忘。闭包和装饰器在很早就了解过,并且还推过一篇文章,但是这几天被提及这个东西,我回答起来居然含糊其辞,没有逻辑。这是一件让人感到非常羞愧的事情。所以我打算用自己的语言再去陈述一遍我对这个东西的见解,也希望大家指出其中的不足或者帮忙扩展,一起进步!·
我们知道在python中一切皆对象,函数和类均是对象,它们可以赋值给一个变量,可以作为入参传递给函数,也可以作为函数的返回值,甚至可以添加到集合对象中去。闭包就是一种在函数内部定义一个新的函数,新函数使用了外部函数定义的变量,并且外部函数返回新函数的引用的方式。
def counter(start=0):
def add_one():
nonlocal start += 1
return start
retrn add_one
c1 = counter(5)
print(c1())
print(c1())
c2 = counter(5)
print(c2())
print(c2())
# 结果:
# 6
# 7
# 6
# 7
当我们调用counter(5)时,返回的是add_one函数的引用,再次调用c1()运行add_one指向的代码块。
我们可以看到,闭包是数据+功能(函数)的结合,而我们常用的类,也是数据(属性)+功能(方法)的结合,类都需要继承object,所以相比较而言,闭包比类更轻量
对于一般的函数而言,在使用完变量后,会自动释放,而通过上例可以看出,在调用c1()后,并未释放内存,在下一次调用时依然使用的是原来定义的变量。闭包会携带包含它的函数的作用域,闭包间函数作用域互不影响,因此会比其它函数占用更多的内存,需要手动释放内存
内部函数只能引用外部函数的局部变量,如果需要修改外部函数的变量,需要使用关键字nonlocal
当函数、匿名函数、闭包、对象 当做实参时,有什么区别?
def test(temp):
pass
def a():
pass
# 相当于传递功能,不是传递数据
test(a)
b = lambda x : x*2
# 相当于传递功能,不是传递数据
test(b)
def person(name):
def say(content):
print(name, content)
return say
p = person("xiaoming")
# 相当于传递了say这个功能以及name这个数据
test(p)
class Persons(object):
def __init__(self, name):
self.name = name
def say(self,content):
print(self.name,content)
#
p2 = Persons("xiaohua")
# 相当于传递了功能以及name这个数据
test(p2)
闭包应用1:一个人站在原点,然后向X、Y轴进行移动,每次移动后及时打印当前的位置
def create():
pos = [0,0]
def player(direction, step):
new_x = pos[0] + direction[0] * step
new_y = pos[1] + direction[1] * step
pos[0] = new_x
pos[1] = new_y
return player
player = create()
print(player([1,0], 10))
print(player([0,1], 20))
print(play([-1,0], 10))
闭包应用2:对某文件的特殊行进行分析,先要提取出这些特殊行
def make_filter(keep):
def the_filter(filter_name):
with open(file_name, 'r'):
lines = file.readlines()
file_doc = [i for i in lines if keep in i]
return fileter_doc
return the_filter
filter = make_filter("163.com")
filter_result = filter("result.txt")
我们可以看以下一个例子,我们想要算出执行一个函数消耗了多少时间,不使用装饰器的情况下,可以这样实现:
import time
def calculate_10w():
'''
计算100000以内的每个数的立方和
:return:
'''
sum_ret = 0
for i in range(1, 100001):
sum_ret += i ** 3
print("10w以内的每个数的立方和为:",sum_ret)
start_time = time.time()
calculate_10w()
stop_time = time.time()
print("耗费总时长为:", stop_time - start_time, "(秒)")
# 结果
# 10w以内的每个数的立方和为: 25000500002500000000
# 耗费总时长为: 0.04386019706726074 (秒)
如果我们需要对多个函数都单独计时,采用上述方法,每执行一个函数都需要在调用函数前后加上计时代码,然后执行一条输出时间语句,这样不仅会造成大量的重复代码,而且代码管理维护也会变得困难。可以观察到,除了需要执行的函数不一样,函数前后执行的语句都是相同的,那我们能不能定义一个方法,将函数作为入参,然后直接调用方法呢
import time
def calculate_10w():
'''
计算100000以内的每个数的立方和
:return:
'''
sum_ret = 0
for i in range(1, 100001):
sum_ret += i ** 3
print("10w以内的每个数的立方和为:",sum_ret)
def caculate_time(fun):
start_time = time.time()
fun()
stop_time = time.time()
print("耗费总时长为:", stop_time - start_time, "(秒)")
caculate_time(calculate_10w)
# 结果
# 10w以内的每个数的立方和为: 25000500002500000000
# 耗费总时长为: 0.04386019706726074 (秒)
显然,重新定义一个函数,将需要计算执行时间的函数作为入参是可行的,也能解决重复代码的问题。但是计算函数执行时间只是我们想要实现的额外功能,运行函数calculate_10w()才是我们最主要的功能,通过上面的方法,calculate_10w()的执行完全隐藏在了caculate_time()函数当中,这样代码的可读性就大大降低了,对于后期代码的维护也没有很好。那还有方法可以更完美地解决这些问题吗?这个时候就引入了装饰器
装饰器是在不改变原函数或者类的功能的情况下,为函数/类添加额外的功能,它本身是个特殊的闭包函数或者类,入参是需要被装饰的函数或者类
import time
def caculate_time(fun):
def inner():
start_time = time.time()
fun()
stop_time = time.time()
print("耗费总时长为:", stop_time - start_time, "(秒)")
return inner
@caculate_time
def calculate_10w():
'''
计算100000以内的每个数的立方和
:return:
'''
sum_ret = 0
for i in range(1, 100001):
sum_ret += i ** 3
print("10w以内的每个数的立方和为:",sum_ret)
calculate_10w()
# 结果
# 10w以内的每个数的立方和为: 25000500002500000000
# 耗费总时长为: 0.04386019706726074 (秒)
我们可以看到,通过上述方式,实现一个装饰器,然后在calculate_10w()函数上添加@caculate_time,再调用calculate_10w(),也能够实现计时器功能,那它是怎么做到的呢?
1、首先定义了一个入参为函数的闭包caculate_time(fun)函数,返回的是它内部的inner函数的引用
2、在需要添加计时功能的函数calculate_10w()上使用@caculate_time
3、调用calculate_10w()函数
4、当python解释器运行到caculate_time(fun)闭包函数时,会将函数加载到内存(只有被调用时才会被执行)
5、当执行到@caculate_time时,会将caculate_time看作可执行对象,去调用caculate_time()函数,并将被修饰的函数引用作为入参传入,将返回的inner引用赋值给calculate_10w,即
calculate_10w = caculate_time(calculate_10w)
5、当解释器执行calculate_10w()时,实际上是在运行inner函数
import time
def caculate_time(fun):
print("--------开始装饰----------")
def inner():
print("-----开始调用原函数------")
start_time = time.time()
fun()
stop_time = time.time()
print("耗费总时长为:", stop_time - start_time, "(秒)")
print("------结束调用原函数------")
print("-----完成装饰-------")
return inner
# @caculate_time
def calculate_10w():
'''
计算100000以内的每个数的立方和
:return:
'''
sum_ret = 0
for i in range(1, 100001):
sum_ret += i ** 3
print("10w以内的每个数的立方和为:",sum_ret)
calculate_10w = caculate_time(calculate_10w)
calculate_10w()
# 结果
# -------开始装饰----------
# ----完成装饰-------
# ----开始调用原函数------
# 0w以内的每个数的立方和为: 25000500002500000000
# 费总时长为: 0.04487943649291992 (秒)
# -----结束调用原函数------
上述例子属于无参装饰器,装饰器不需要传入任何参数,如果我们需要使用参数,又不确定需要被装饰的函数的入参个数和类型,可以在装饰器的内函数中使用*args和**kwargs作为入参。当调用原函数的时候,实参会传递到闭包中的内部函数的形参变量中,在内部函数执行的时候,将这些数据作为实参传递到原函数中
def timefun(func):
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
@timefun
def foo(a, b):
print(a+b)
@timefun
def count(a, b, c):
print(a+b+c)
foo(1,2)
count(1,2,3)
# 结果
# 3
# 6
装饰器内部函数有return返回,则为有返回值装饰器,一般返回一个数据,也可以返回多个数据,相应地,装饰器内部函数没有return返回值,则是无返回值装饰器
def test(func):
def inner(*args, **kwargs):
res1 = func(*args, **kwargs)
res2 = 5
return res1,res2
return inner
@test
def myfunc(num):
return num
res1,res2 = myfunc(4)
print(res1)
print(res2)
# 结果
# 4
# 5
如果我们还希望装饰器也能够传入一些参数,可以这样做:
import time
def outter(timeout=0):
def wrapper(func):
def inner(*args, **kwargs):
print("开始执行函数")
time.sleep(timeout)
res = func(*args, **kwargs)
print("函数运行结束")
return res
return inner
return wrapper
@outter(3)
def test(num):
return num
res = test(5)
print(res)
# 结果
# 开始执行函数
# 函数运行结束
# 5
执行@outter(3),得到wrapper引用,然后会执行test = wrapper(test),得到inner的引用;调用test(5),执行inner函数,得到返回值5
以上都是封装的函数装饰器,除了函数可以作为装饰器,类也能够作为装饰器来使用,基本的使用方式和函数装饰器类似。我们来看下面的例子:
class Test(object):
def __init__(self,func):
print("-----初始化------")
print("func name is %s" % func.__name__)
self.__func = func
def __call__(self):
print("-----装饰器中的功能----")
self.__func()
@Test
def mytest():
print("----mytest----")
mytest()
# 结果
# -----初始化------
# func name is mytest
# -----装饰器中的功能----
# ----mytest----
执行@Test时,相当于mytest = Test(mytest),即实例化了一个Test对象,并将mytest函数引用作为初始化参数传入__init__中,再将对象的引用赋值给mytest。当运行mytest()时,相当于调用了Test的实例对象,会自动调用并执行魔法函数__call__,从而运行原来的mytest函数