Python装饰器真是个很有用的武器呢!一定要理解透彻,因为面试会让你写装饰器的,比如加锁的装饰器,统计函数被调用的装饰器!
python 函数的特性
1,函数可以作为参数
def func():
return "func"
def test_func(x):
return x()
A=func
res=test_func(A)
print(res)
结果:func
2,函数可以作为返回值
def func():
return "func"
def test(x):
return x
A=func
res=test(A)
print(res()) #func
3,函数可以作为变量
def func():
return "func"
A=func
res=A()
print(res) #func
4,函数可以嵌套及跨越访问
def test_func():
x="test_func"
def test_inner():
print(x)
return test_inner()
test=test_func() #test_func
闭包
闭包就是引用了外部函数的变量的内部函数,而闭包的实现正是利用了以上函数特性,下面我们来看看闭包是如何实现的:
def outer(x):
def inner(): # 函数嵌套
return x # 跨域访问,引用了外部变量x
return inner # 函数作为返回值
closure = outer('外部变量') # 函数作为变量赋给closure
print(closure()) # 执行闭包
# 外部变量
装饰器与装饰器语法糖
装饰器就是利用闭包实现,使用函数作为参数。
def test_func(f):
x="test_func"
def test_inner():
print("docorator")
f()
return test_inner
def func():
print("func")
deco_test=test_func(func)
deco_test()
#运行结果
#docorator
#func
@装饰器语法糖
def test_func(f):
x="test_func"
def test_inner():
print("docorator")
f()
return test_inner
@test_func #实质是:test_func(func)
def func():
print("func")
func() #实质是:test_func(func)(),即test_inner()
#运行结果
#docorator
#func
装饰器是在转载的时候被运行的,@test_func使用在func函数上,实质是对装饰器的调用:test_func(func)
自由变量及nonlocal的使用
上面函数计数的例子中两个变量有料!coun与 count_l 在内层函数的赋值大不同
coun是外层函数的局部变量,对内部函数(闭包函数)来说是自由变量,可以使用,但是不可以赋值,要赋值的话,必须使用nonlocal。
nonlocal的作用:申明此变量为自由变量,声明的变量可以赋予新值,闭包中保存的绑定会更新。
list类型的,不需要使用nonlocal,为什么可以在内层函数使用append()方法?
因为append并没有给列表赋值,只是追加了内容。利用了列表是可变对象
装饰器函数
一个被类的装饰器修饰的方法就是一个类的实例。
类装饰器离不开__call__魔术方法的对象,实现了__call__魔术方法的对象是可调用的,能够使得允许一个类的实例像函数一样被调用
# 函数是可调用对象
def add():
pass
add.__call__()
add()
# 类实现了__call__方法
class Add():
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
add_instance = Add()
add_instance.__call__(1,2,3, course='Python')
add_instance(1,2,3,course='Python')
call:允许一个类的实例像函数一样被调用:x(a, b)调用为 x.call(a, b)
class CallingCounter(object):
def __init__ (self, func):
self.func = func
self.count = 0
def __call__ (self, *args, **kwargs):
print("类--当前被执行的函数名为:{}".format(self.func.__name__))
self.count += 1
print("{}被调用{}次".format(self.func.__name__,self.count))
return self.func(*args, **kwargs)
@CallingCounter #实质是CallingCounter(func),一个被类的装饰器修饰的方法就是一个类的实例
def func():
print("func")
func()
func()
func()
打印内容:
类–当前被执行的函数名为:func
func被调用1次
func
类–当前被执行的函数名为:func
func被调用2次
func
类–当前被执行的函数名为:func
func被调用3次
func
装饰器的实例
1, 计算函数被调用次数的装饰器:
def func_count(func):
print("我是装饰器,我被调用了。。。")
coun=[]
count_l=0
def _call_times(*args,**kwargs):
print("函数--当前被执行的函数名为:{}".format(func.__name__))
nonlocal count_l
count_l+=1
coun.append(1)
print("我被调用了{}次".format(coun))
print("***我被调用了{}次".format(count_l))
return func(*args,**kwargs)
return _call_times
装饰器使用语法糖,加载在两个函数上
@func_count
def test():
return 1+1
@func_count
def test_test():
return 3
即使函数没有被执行,打印的内容如下:
我是装饰器,我被调用了。。。
我是装饰器,我被调用了。。。
@func_count
def test():
函数头部@装饰器后,实质是装饰器的调用:func_count(test),所以如上信息被打印
运行函数
test()
test()
test()
test_test()
结果:
我是装饰器,我被调用了。。。
我是装饰器,我被调用了。。。
函数–当前被执行的函数名为:test
我被调用了[1]次
***我被调用了1次
函数–当前被执行的函数名为:test
我被调用了[1, 1]次
***我被调用了2次
函数–当前被执行的函数名为:test
我被调用了[1, 1, 1]次
***我被调用了3次
函数–当前被执行的函数名为:test_test
我被调用了[1]次
***我被调用了1次
2,替换函数
有时候更改旧代码,发现个别处的函数功能需要替换,但是这个函数很多地方已经被调用了,不想改动那么多地方的话,又不想改变调用关系及函数名称,这个时候可以使用装饰器来改变函数功能
import inspect
def respeat_func_dec(replace_func):
print("replace_func:",replace_func.__name__)
def decor(func_):
print("func_:", func_.__name__)
def wrap(*args, **kwargs):
print("wrap")
return replace_func(**inspect.getcallargs(func_, *args, **kwargs))
return wrap
return decor
def new_func(name="",age=0):
print(f"name={name}")
@respeat_func_dec(new_func) #实质是:doc_func(new_func)(old_func)
def old_func(name="",age=0):
print(f"age={age}")
old_func(name="xiaoyu",age=2)
打印:
replace_func: new_func
func_: old_func
执行装饰器:
old_func(name="xiaoyu",age=2) #实质是:doc_func(new_func)(old_func)(name="xiaoyu",age=2)
打印:
replace_func: new_func
func_: old_func
wrap
name=xiaoyu