此文多是有史以来最全的关于Python装饰器的Blog了...面试
函数名是⼀个变量,但它是⼀个特殊的变量。与括号配合能够执⾏函数的变量。缓存
查看函数名的内存地址:闭包
def func(): print('呵呵') print(func) # <function func at 0x10983c048>
def func(): print('呵呵') a = func # 把函数当成变量赋值给另一个变量 a() # 经过变量a调用函数
def func1(): print('func1') def func2(): print('func2') def func3(): print('func3') def func4(): print('func4') list1 = [func1, func2, func3, func4] for i in list1: i()
def func1(): print('func1') def func2(arg): print('start') arg() # 执行传递进来的arg print('end') func2(func1) # 把func1当成参数传递给func2
def func1(): print('这里是func1') def func2(): print('这里是func2') return func2 # 把func2当成返回值返回 ret = func1() # 调用func1,把返回值赋值给ret ret() # 调用ret
首先咱们来看一个例子:函数
def func1(): name = '张三' def func2(arg): print(arg) func2(name) func1()
理解了上面的例子,咱们再看一个例子:工具
def func1(): name = '张三' def func2(): print(name) # 可以访问到外层做用域的变量 func2() func1()
最后再看一个例子:spa
def func1(name): def func2(): print(name) # 可以访问到外层做用域的变量 func2() func1('张三')
一个内层函数中,引用了外层函数(非全局)的变量,这个内层函数就能够成为闭包。设计
在Python中,咱们可使用__closure__来检测函数是不是闭包。code
def func1(): name = '张三' def func2(): print(name) # 可以访问到外层做用域的变量 func2() print(func2.__closure__) # (<cell at 0x1036c7438: str object at 0x10389d088>,) func1() print(func1.__closure__) # None
问题来了,咱们如何在函数外边调用函数内部的函数呢?orm
固然是把内部函数当成返回值返回了。blog
def func1(): name = '张三' def func2(): print(name) return func2 # 把内部函数当成是返回值返回 ret = func1() # 把返回值赋值给变量ret ret() # 调用内部函数
内部函数固然还可包含其余的函数,多层嵌套的道理都是同样的。
def func1(): def func2(): def func3(): print('func3') return func3 return func2 ret1 = func1() # func2 ret2 = ret1() # func3 ret2()
接下来咱们看下面这个例子,来更深入的理解一下闭包的含义:
def print_msg(msg): # 这是外层函数 def printer(): # 这是内层函数 print(msg) return printer # 返回内层函数 func = print_msg("Hello") func()
如今咱们进行以下操做:
>>> del print_msg >>> func() Hello >>> print_msg("Hello") Traceback (most recent call last): ... NameError: name 'print_msg' is not defined
咱们知道若是⼀个函数执⾏完毕,则这个函数中的变量以及局部命名空间中的内容都将会被销毁。在闭包中内部函数会引用外层函数的变量,并且这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。
也就是说,闭包函数能够保留其用到的变量的引用。
# 编写代码实现func函数,使其实现如下效果: foo = func(8) print(foo(8)) # 输出64 print(foo(-1)) # 输出-8
在说装饰器以前,咱们先说⼀个软件设计的原则: 开闭原则, ⼜被成为开放封闭原则。
开放封闭原则是指对扩展代码的功能是开放的,可是对修改源代码是封闭的。这样的软件设计思路能够保证咱们更好的开发和维护咱们的代码。
咱们先来写一个例子,模拟一下女娲造人:
def create_people(): print('女娲真厉害,捏个泥吹口气就成了人!') create_people()
好吧,如今问题来了。上古时期啊,天气很不稳定,这个时候忽然大旱三年。女娲再去捏人啊,由于太干了就捏不到一起去了,须要在捏人以前洒点水才行。
def create_people(): print('洒点水') print('女娲真厉害,捏个泥吹口气就成了人!') create_people()
这不就搞定了么?可是呢,咱们是否是违背了开放封闭原则呢?咱们是添加了新的功能,可是咱们是直接修改了源代码。在软件开发中咱们应该对直接修改源代码是谨慎的。
好比,女娲为了防止浪费,想用剩下点泥巴捏个鸡、鸭、鹅什么的,也须要洒点水。那咱们能在每一个造鸡、造鸭、造鹅函数的源代码中都手动添加代码么?确定是不现实的。
怎么办?再写一个函数不就OK了么?
def create_people(): print('女娲真厉害,捏个泥吹口气就成了人!') def create_people_with_water(): print('洒点水') create_people() create_people_with_water()
不让我直接修改源代码,那我从新写一个函数不就能够了吗?
可是,你有没有想过一个问题,女娲造人也很累的,她后来开了不少分店,每家分店都是调用了以前的create_people函数造人,那么你修改了以后,是否是全部调用原来函数的人都须要修改调用函数的名称呢?很麻烦啊!!!
总结一句话就是如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能?
def create_people(): print('女娲真厉害,捏个泥吹口气就成了人!') def a(func): def b(): print('洒点水') func() return b ret = a(create_people) ret()
利用闭包函数不就能够了么?
可是,你这最后调用的是ret啊,不仍是改变了调用方式么?
再往下看:
def create_people(): print('女娲真厉害,捏个泥吹口气就成了人!') def a(func): def b(): print('洒点水') func() return b create_people = a(create_people) create_people()
上面这段代码是否是完美解决了咱们的问题呢?
看一下它的执行过程吧:
咱们巧妙的使用闭包实现了,把一个函数包装了一下,而后再赋值给原来的函数名。
上面的代码就是一个装饰器的雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。
使用装饰器语法糖的写法,实现一样功能的代码以下:
def a(func): def b(): print('洒点水') func() return b @a # 装饰器语法糖 def create_people(): print('女娲真厉害,捏个泥吹口气就成了人!') create_people()
若是被装饰的函数有返回值,咱们应该怎么处理呢?
请看下面的示例:
def foo(func): # 接收的参数是一个函数名 def bar(): # 定义一个内层函数 print("这里是新功能...") # 新功能 r = func() # 在内存函数中拿到被装饰函数的结果 return r # 返回被装饰函数的执行结果 return bar # 定义一个有返回值的函数 @foo def f1(): return '嘿嘿嘿' # 调用被装饰函数 ret = f1() # 调用被装饰函数并拿到结果 print(ret)
def foo(func): # 接收的参数是一个函数名 def bar(x, y): # 这里须要定义和被装饰函数相同的参数 print("这里是新功能...") # 新功能 func(x, y) # 被装饰函数名和参数都有了,就能执行被装饰函数了 return bar # 定义一个须要两个参数的函数 @foo def f1(x, y): print("{}+{}={}".format(x, y, x+y)) # 调用被装饰函数 f1(100, 200)
被装饰的函数能够带参数,装饰器一样也能够带参数。
回头看咱们上面写得那些装饰器,它们默认把被装饰的函数当成惟一的参数。可是呢,有时候咱们须要为咱们的装饰器传递参数,这种状况下应该怎么办呢?
接下来,咱们就一步步实现带参数的装饰器:
首先咱们来回顾下上面的代码:
def f1(func): # f1是咱们定义的装饰器函数,func是被装饰的函数 def f2(*arg, **kwargs): # *args和**kwargs是被装饰函数的参数 func(*arg, **kwargs) return f2
从上面的代码,咱们发现了什么?
个人装饰器若是有参数的话,没地方写了…怎么办呢?
仍是要使用闭包函数!
咱们须要知道,函数除了能够嵌套两层,还能嵌套更多层:
# 三层嵌套的函数 def f1(): def f2(): name = "张三" def f3(): print(name) return f3 return f2
嵌套三层以后的函数调用:
f = f1() # f --> f2 ff = f() # ff --> f3 ff() # ff() --> f3() --> print(name) --> 张三
注意:在内部函数f3中可以访问到它外层函数f2中定义的变量,固然也能够访问到它最外层函数f1中定义的变量。
# 三层嵌套的函数2 def f1(): name = '张三' def f2(): def f3(): print(name) return f3 return f2
调用:
f = f1() # f --> f2 ff = f() # ff --> f3 ff() # ff() --> f3() --> print(name) --> 张三
好了,如今咱们就能够实现咱们的带参数的装饰器函数了:
# 带参数的装饰器须要定义一个三层的嵌套函数 def d(name): # d是新添加的最外层函数,为咱们原来的装饰器传递参数,name就是咱们要传递的函数 def f1(func): # f1是咱们原来的装饰器函数,func是被装饰的函数 def f2(*arg, **kwargs): # f2是内部函数,*args和**kwargs是被装饰函数的参数 print(name) # 使用装饰器函数的参数 func(*arg, **kwargs) # 调用被装饰的函数 return f2 return f1
上面就是一个带参装饰器的代码示例,如今咱们来写一个完整的应用:
def d(a=None): # 定义一个外层函数,给装饰器传参数--role def foo(func): # foo是咱们原来的装饰器函数,func是被装饰的函数 def bar(*args, **kwargs): # args和kwargs是被装饰器函数的参数 # 根据装饰器的参数作一些逻辑判断 if a: print("欢迎来到{}页面。".format(a)) else: print("欢迎来到首页。") # 调用被装饰的函数,接收参数args和kwargs func(*args, **kwargs) return bar return foo @d() # 不给装饰器传参数,使用默认的'None'参数 def index(name): print("Hello {}.".format(name)) @d("电影") # 给装饰器传一个'电影'参数 def movie(name): print("Hello {}.".format(name)) if __name__ == '__main__': index('张三') movie('张三')
被装饰的函数最终都会失去原本的__doc__等信息, Python给咱们提供了一个修复被装饰函数的工具。
def a(func): @wraps(func) def b(): print('洒点水') func() return b @a # 装饰器语法糖 def create_people(): """这是一个女娲造人的功能函数""" print('女娲真厉害,捏个泥吹口气就成了人!') create_people() print(create_people.__doc__) print(create_people.__name__)
同一个函数能够被多个装饰器装饰,此时须要注意装饰器的执行顺序。
def foo1(func): print("d1") def inner1(): print("inner1") return "<i>{}</i>".format(func()) return inner1 def foo2(func): print("d2") def inner2(): print("inner2") return "<b>{}</b>".format(func()) return inner2 @foo1 @foo2 def f1(): return "Hello Andy" # f1 = foo2(f1) ==> print("d2") ==> f1 = inner2 # f1 = foo1(f1) ==> print("d1") ==> f1 = foo1(inner2) ==> inner1 ret = f1() # 调用f1() ==> inner1() ==> <i>inner2()</i> ==> <i><b>inner1()</b></i> ==> <i><b>Hello Andy</b></i> print(ret)
咱们除了可使用函数装饰函数外,还能够用类装饰函数。
class D(object): def __init__(self, a=None): self.a = a self.mode = "装饰" def __call__(self, *args, **kwargs): if self.mode == "装饰": self.func = args[0] # 默认第一个参数是被装饰的函数 self.mode = "调用" return self # 当self.mode == "调用"时,执行下面的代码(也就是调用使用类装饰的函数时执行) if self.a: print("欢迎来到{}页面。".format(self.a)) else: print("欢迎来到首页。") self.func(*args, **kwargs) @D() def index(name): print("Hello {}.".format(name)) @D("电影") def movie(name): print("Hello {}.".format(name)) if __name__ == '__main__': index('张三') movie('张三')
咱们上面全部的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。
可使用装饰器,来批量修改被装饰类的某些方法:
# 定义一个类装饰器 class D(object): def __call__(self, cls): class Inner(cls): # 重写被装饰类的f方法 def f(self): print('Hello 张三.') return Inner @D() class C(object): # 被装饰的类 # 有一个实例方法 def f(self): print("Hello world.") if __name__ == '__main__': c = C() c.f()
举个实际的应用示例:
咱们把类中的一个只读属性定义为property属性方法,只有在访问它时才参与计算,一旦访问了该属性,咱们就把这个值缓存起来,下次再访问的时候无需从新计算。
class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): if instance is None: return self else: value = self.func(instance) setattr(instance, self.func.__name__, value) return value import math class Circle: def __init__(self, radius): self.radius = radius @lazyproperty def area(self): print('计算面积') return math.pi * self.radius ** 2 c1 = Circle(10) print(c1.area) print(c1.area)