在之前的文章中写了Python的生成器和迭代器,今天给大家分享一下关于装饰器的一些知识。
闭包
在讲装饰器之前一定要提及的就是闭包,因为Python中的闭包是实现装饰器的基础。
# 定义一个函数
def test(number):
# 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包
def test_in(number_in):
print("in test_in 函数, number_in is %d" % number_in)
return number+number_in
# 其实这里返回的就是闭包的结果,也就是返回test_in这个内部函数的引用
return test_in
# 给test函数赋值,这个20就是给参数number
# 这里的ret实际上接收的是运行完test之后的test_in的引用
ret = test(20)
# 注意这里的100其实给参数number_in,ret(100)在这里等价于test_in(100)
print(ret(100))
#注 意这里的200其实给参数number_in
print(ret(200))
运行结果:
in test_in 函数, number_in is 100
120
in test_in 函数, number_in is 200
220
从上面的例子我们可以得出一些结论:
闭包= 内部函数 + 自由变量
闭包的3个特点:
1 函数的嵌套定义
2 外部函数返回内部函数的引用
3 内部函数可以使用外部函数提供的自由变量
闭包的另一个实例:
def line_conf(a, b):
def line(x):
return a*x + b
return line
#注意这里的line1和line2是两个独立运行的空间互不影响
line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))
这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。
如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。
注意点:
由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存。
修改外部函数的变量
#python3的方法
def counter(start=0):
def incr():
nonlocal start
start += 1
return start
return incr
c1 = counter(5)
print(c1())
#python2 的方法 python2的方法实际并没有修改start的值,只是利用了bug.
def counter(start=0):
count = [start]
def incr():
count[0] += 1
return count[0]
return incr
c1 = counter(5)
print(c1())
闭包的总结:
函数名只是函数代码空间的引用,当函数名赋值给一个对象的时候 就是引用传递
闭包就是一个嵌套定义的函数,在外层运行时才开始内层函数的定义,然后将内部函数的引用传递函数外的对象
内部函数和使用的外部函数提供的变量构成的整体称为闭包
装饰器
在初步了解完毕闭包之后,我们就可以来看看装饰器到底是什么样的了。
装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题。
装饰器相对于其他是一个比较难明白的一个知识点,但是这是开发必备的基础,也是作为一个python程序员必须掌握的。
#### 1 ####
def foo():
print('foo')
foo # 表示是函数
foo() # 表示执行foo函数
#### 2 ####
def foo():
print('foo')
foo = lambda x: x + 1
foo() # 执行的是lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数,也就是foo成为了这个匿名函数的引用
装饰器究竟是为了实现什么功能而被开发出来的呢?
写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现的功能代码块
开放:对扩展开发
根据这个原则如果要在函数模块添加功能就不能在内部再添加代码了,这里装饰器就派上用场了。
我们在下面的代码中会将f1函数添加一个验证的功能:
def yanzheng(func):
"""装饰器函数的特点: 基于闭包实现的 有且只有一个参数用于保存被装饰的函数的引用"""
#这个参数接收的一定是被增加功能的函数的引用
def inner():
print("验证1......")
func()
return inner
# 在不修改代码的情况下 对代码进行功能的扩展
# 装饰过程: 1 把需要被装饰的函数的引用传入到 装饰器函数内部保存 func中
# 2 创建内部函数 在其中调用func
# 3 在调用func之前可以扩展 新功能
# 4 将装饰器函数内部的 内部函数的引用传给 f1<此时的f1就是装饰功能后的f1>
# 5 对于用户来讲调用f1就可以调用 f1原有函数的功能
# 6 实际上调用f1是执行的inner函数 inner内部中保存的有f1原有的引用
# @装饰器函数名 语法只是一个语法糖 对灵魂代码的一种语法封装
@yanzheng #相当于f1 = yanzheng(f1)在这里两个f1是不一样的,右边f1是下面f1这个函数的引用,
而左边f1在传入yanzheng函数传入f1参数之后成为了内部函数inner的引用。在inner函数中我们调用了原本的f1函数,并且附加了打印验证的功能。
def f1():
print("in f1")
f1()
无参数的函数
无参数的函数
from time import ctime, sleep
def timefun(func):
def wrapped_func():
print("%s called at %s" % (func.__name__, ctime()))
func()
return wrapped_func
@timefun
def foo():
print("I am foo")
foo()
sleep(2)
foo()
上面代码理解装饰器执行行为可理解成
foo = timefun(foo)
# foo先作为参数赋值给func后,foo接收指向timefun返回的wrapped_func
foo()
# 调用foo(),即等价调用wrapped_func()
# 内部函数wrapped_func被引用,所以外部函数的func变量(自由变量)并没有释放
# func里保存的是原foo函数对象
有参数的函数
被装饰的函数有参数
from time import ctime, sleep
def timefun(func):
def wrapped_func(a, b):
print("%s called at %s" % (func.__name__, ctime()))
print(a, b)
func(a, b)
return wrapped_func
@timefun
def foo(a, b):
print(a+b)
foo(3,5)
sleep(2)
foo(2,4)
被装饰的函数有不定长参数
from time import ctime, sleep
def timefun(func):
def wrapped_func(*args, **kwargs):
print("%s called at %s"%(func.__name__, ctime()))
func(*args, **kwargs)
return wrapped_func
@timefun
def foo(a, b, c):
print(a+b+c)
foo(3,5,7)
sleep(2)
foo(2,4,9)
装饰器中的return
from time import ctime, sleep
def timefun(func):
def wrapped_func():
#先用info 接收一下info 的返回值等待之后的添加的功能执行完毕后再返回
info = func()
print("%s called at %s" % (func.__name__, ctime()))
return info
return wrapped_func
@timefun
def foo():
print("I am foo")
@timefun
def getInfo():
return '----hahah---'
foo()
sleep(2)
foo()
print(getInfo())
装饰器带参数,在原有的基础上,添加外部变量。
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrapped_func():
print("%s called at %s %s" % (func.__name__, ctime(), pre))
return func()
return wrapped_func
return timefun
# 下面的装饰过程
# 1. 调用timefun_arg("python")
# 2. 将步骤1得到的返回值,即time_fun返回, 然后time_fun(foo)
# 3. 将time_fun(foo)的结果返回,即wrapped_func
# 4. 让foo = wrapped_fun,即foo现在指向wrapped_func
# 过程整体可以理解为:把timefun_arg('python')当做一个外部函数的整体,在执行这个函数之后,这个整体就相当于timefun(func),就又变回了我们的基本装饰器的定义了。而传入的python参数也可以作为一个自由变量传入内部函数当中使用。
@timefun_arg("python") #foo = timefun_arg('python')(foo)
def foo():
print("I am foo")
foo()
sleep(2)
foo()
可以理解为
foo()==timefun_arg("itcast")(foo)()
类装饰器
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---装饰器中的功能---")
self.__func()
#说明:
#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象
# 并且会把test这个函数名当做参数传递到__init__方法中
# 即在__init__方法中的属性__func指向了test指向的函数
#
#2. test指向了用Test创建出来的实例对象
#
#3. 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法
#
#4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
# 所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体
@Test
def test():
print("----test---")
test()
showpy()#如果把这句话注释,重新运行程序,依然会看到"--初始化--"
多个装饰器装饰一个函数(较难理解)
# 定义函数:完成包裹数据
def makeBold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
# 定义函数:完成包裹数据
def makeItalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeBold
def test1():
return "hello world-1"
@makeItalic
def test2():
return "hello world-2"
@makeBold
@makeItalic
def test3():
return "hello world-3"
#这里我们拆分理解下
test3 = makeBold(test3)
test3 = makeItalic(test3)
程序的执行过程如下,程序会就近原则先执行test3 = makeItalic(test3)这个装饰器,那么执行出来的结果test3会是makeItalic中的weapped()的引用,再返回执行上一行的test3 = makeBold(test3),这里加入的参数test3已经变成了makeItalic中的wrapped()的引用,最后test3指向的是warpped()的引用。
所以在执行test3()的时候的执行流程为,先执行<b>,然后再执行fn()而此时的fn指向的是makeItalic中的weapped()的引用,接着就会执行<i>然后执行makeItalic的fn,输出结果hello world-3,fn执行完了再执行</i>注意此时只是makeItalic中warapped()的fn执行完了,哦后面还有</i>将其输出,此时全部返回。就得到了下面的结果:<b><i>hello world-3</i></b>。
总结出来就是:在装饰器中实际的代码是从下到上,实现的过程是从上到下。
print(test1())
print(test2())
print(test3())
运行结果:
<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
总结:
- 装饰器函数只有一个参数就是被装饰的函数的应用
- 装饰器能够将一个函数的功能在不修改代码的情况下进行扩展
- 在函数定义的上方@装饰器函数名 即可直接使用装饰器对下面的函数进行装饰。