闭包
def outer():
x = 10
def inner(): # 内部函数
print(x) # 引用外部变量
return inner # inner是闭包
f = outer()
f() # f在outer()外执行,却能取到其内部变量x
如果在一个内部函数中,对外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure
)。
闭包 = 函数快 + 定义函数时的环境
装饰器概念
装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
先来看一个简单例子:
def foo():
print('I am fool')
现在有一个新的需求,希望可以记录下函数的执行前后的时间,于是在代码中添加日志代码
import time, logging
def fool():
print("Begin:", time.ctime())
print('I am fool')
print("End:", time.ctime())
foo()
bar()
、bar2()
也有类似的需求,怎么做?再在bar
函数里调用时间函数?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门设定时间
import time
def show_time(func):
print("Begin:", time.ctime())
func()
print("End", time.ctime())
def bar():
print("I am bar")
show_time(bar)
逻辑上不难理解,而且运行正常。 但是这样的话,我们每次都要将一个函数作为参数传递给show_time
函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar()
,但是现在不得不改成show_time(bar)
。那么有没有更好的方式的呢?当然有,答案就是装饰器。
简单装饰器
如果令bar
等于show_time(bar)
,就不要改动以前的代码了。所以,我们需要show_time(bar)
返回一个函数对象,而这个函数对象内则是核心业务函数:func()
与装饰函数:两个时间函数,修改如下:
import time
def show_time(func):
def wrapper():
print("Begin:", time.ctime())
func()
print("End", time.ctime())
return wrapper
def bar():
print("I am bar")
bar = show_time(bar)
bar()
函数show_time
就是装饰器,它把真正的业务方法func
包裹在函数里面,看起来像bar
被上下时间函数装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
@
符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。
import time
def show_time(func):
def wrapper():
print("Begin:", time.ctime())
func()
print("End", time.ctime())
return wrapper
@showtime # bar = show_time(bar)
def bar():
print('I am bar')
@show_time # bar2 = show_time(bar2)
def bar2():
print('I am bar2')
bar()
bar2()
如上所示,这样我们就可以省去bar = show_time(bar)
这一句了,直接调用bar()
即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
带参数的被装饰函数
import time
def show_time(func):
def wrapper(a, b):
print("Begin:", time.ctime())
func(a, b)
print("End:", time.ctime())
return wrapper
@show_time
def add(a, b):
print(a+b)
add(2, 3)
即:被装饰的功能函数带有参数时,只需在wrapper()
内对应加上参数即可
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@show_time
,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)
。这样,就为装饰器的编写和使用提供了更大的灵活性。
import time
def cal_time(flag):
def show_time(func):
def wrapper(a, b):
start = time.ctime()
print("Begin:", start)
func(a, b)
end = time.ctime()
print("End:", end)
if flag:
print("Expend time:", time.mktime(time.strptime(end))-time.mktime(time.strptime(start)))
return wrapper
return show_time
@cal_time(True)
def add(a, b):
c = a + b
time.sleep(1)
print(c)
add(1, 2)
上面的cal_time
是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@cal_time("true")
调用的时候,Python
能够发现这一层的封装,并把参数传递到装饰器的环境中。
类装饰器
再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__
方法,当使用 @
形式将装饰器附加到函数上时,就会调用此方法。
class Foo():
def __init__(self, func):
self.func = func
def __call__(self):
print('class decorator running')
self.func()
print('class decorator ending')
@Foo
def bar():
print('bar')
bar()
++++++++++++++++++++++++++++++
class decorator running
bar
class decorator ending
functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring
、__name__
、参数列表,先看例子:
def foo():
print('foo')
print(foo.__name__)
def logged(func):
def wrapper(*args, **kwars):
print(func.__name__ + 'was called')
return func(*args, **kwargs)
return wrapper
@logged
def bar(x):
return x + x * x
print(bar.__name__)
++++++++++++++++++++++++++++++
foo
wrapper
函数
@logged
def bar(x):
return x + x * x
等价于
def bar(x):
return x + x * x
bar = logged(bar)
不难发现,函数bar
被wrapper
取代了,于是其docstring
,__name__
就变成了wrapper
函数的信息了。
这个问题可能引起一定的麻烦,好在我们有functools.wraps
,wraps
本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wraps
def foo():
print('foo')
print(foo.__name__)
def logged(func):
@wraps(func) # 只需加上这一句
def wrapper(*args, **kwars):
print(func.__name__ + 'was called')
return func(*args, **kwargs)
return wrapper
@logged
def bar(x):
return x + x * x
print(bar.__name__)
++++++++++++++++++++++++++++++
foo
bar
内置装饰器
@staticmathod
@classmethod
@property
装饰器的顺序
@a
@b
@c
def f ():
等效于
f = a(b(c(f)))