深入浅出装饰器

装饰器(Decorate)是Python中较为晦涩难懂的一节,并且在大量的场景中都有应用,如插入日志,性能测试等,各类框架中也有装饰器的应用。其主要作用是在为对象添加功能的基础上,将大量与对象无关的重复代码抽离出来并加以复用,简而言之,就是在不修改对象的前提下,为已定义对象添加额外功能。
举个栗子,我们为函数加一个输出日志

def log(func):
    def warper(*args,**kwargs):
        print('now running')
        return func(*args,**kwargs)    
    return warper  
@log
def say_hello():
    print('Hello World')

say_hello()
now running
Hello World

这里的log()函数就是一个简单的装饰器。


Pyhton中一切皆对象

在Python中我们创建的的列表,元组,字典等都可以称之为对象,而我们创建的函数也可以称之为一个对象。

def func():  
    print('hello')

a = func
a()
print(a.__name__)
print(func.__name__)
hello
func
func


func与a同时指向了func()函数对象。函数对象被函数名所引用,我们同样可以用其他变量名去引用这个函数对象。

高阶函数:函数对象作为参数

既然变量可以指向函数,而函数可以接受变量作为参数,那函数当然也可以接受另一个函数作为参数,这种接受函数作为参数的函数我们称为高阶函数

def f(func):
    return func()
def roo():
    print('hi')
f(roo)
hi


roo作为参数传递给warper函数,传参类似于func = roo,当执行return func(),相当于执行了return roo(),最后将roo函数对象返回值返回。注意:一个函数作为参数传递给另一个函数,针对的都是函数对象本身
在这里有一个比较容易混淆的问题,例如

def New():
    return 'new'
a = New # _将函数对象赋值给a 
b = New()#调用函数,将函数结果值赋给b
print(type(a))
print(type(b))
<class 'function'>
<class 'str'>

a引用了函数对象,而b引用了函数的返回值。所以在上例中的return func与return func()是不同的概念,前者是返回函数对象,后者是返回函数返回值。

高阶函数:函数对象作为返回值

上面我们可以看出高阶函数不仅可以接受函数作为参数还可以将函数对象作为返回值。

def coo():
    print('coco')
def call_a(func):
    return func
print(type(call_a(coo)))
<class 'function'>

根据上文可以简单理解得出call_a(coo)=>coo,coo函数对象作为参数传给call_a并返回自身函数对象。可以尝试print(call_a(coo).__name__,coo.__name__)看下结果。

现在我们可以尝试再写一个“装饰器”

def warper(func):
    print('now running')
    return func
def say_hello():
    print('Hello World')

say_hello = warper(say_hello)


say_hello()
now running
Hello World

注意:从结果上看我们近乎模拟出了装饰器效果但是仔细观察你会发现,在say_hello = warer(say_hello)这行语句不仅仅是赋值操作,在赋值过程中先执行了print('now running')这行语句。
执行过程:
1. say_hello = warper(say_hello) #先print('now running')再将say_hello函数对象赋值给变量名say_hello
2. say_hello() #执行say_hello函数对象print语句

我们添加一行代码就可以看出来

say_hello = warper(say_hello)
print('a')
say_hello()
now running
a
Hello World

我们需要通过直接调用函数的形式完成装饰器效果,设想把warper(func)封装在一个函数中,通过调用这个函数来实现我们需要的效果,这时候我们就需要用到闭包

函数嵌套:闭包

在Python中可以实现函数嵌套,即在函数内部定义函数,比如

def f1():
    def f2(x):
        return x
    print(f2(5))
f1()
print(f2(5))

打印结果

5
Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\func.py", line 6, in <module>
    f2(5)
NameError: name 'f2' is not defined

f2在f1函数内部定义,我们调用f1函数时正常输出,但是当我们调用f2函数时却会报错。这是因为函数对象在定义后有自己的作用范围,也就是函数对象作用域

LEGB法则

在Python中对象作用域遵循LEGB法则
* L (Local) 局部作用域
* E (Enclosing) 闭包函数外的函数中
* G (Global) 全局作用域
* B (Built-in) 内建作用域

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
栗子:

def roo():
    x = 1     #Enclosing        
    def fun(a):
        return 2*a + x
    return fun
x = 3
my_roo = roo()
print(my_roo(2))

根据LEGB法则,fun函数先查找局部作用域,然后在查找Enclosing作用域,找到变量x并引用,我们称x为fun的环境变量。而在x = 3并没有被引用,这就构成了一个闭包。
而闭包的价值就在于:封存函数上下文环境,这一特性被运用在装饰器函数中。
我们再写一个模拟装饰器函数

def log():
    def wraper(*args,**kwargs):
        print('now running')
        return say_hello(*args,**kwargs)
    return wraper
def say_hello():
    print('Hello World')

say_hello = log()
say_hello()
now running
Hello World

在装饰器中@属于Python的语法糖(Syntax sugar),上文装饰器例子中

@log
def say_hello():
    ...

相当于执行了say_hello = log(say_hello)
最后就有

def log():
    def wraper(*args,**kwargs):
        print('now running')
        return say_hello(*args,**kwargs)
    return wraper
def say_hello():
    print('Hello World')

@log
def say_hello():
    print('Hello World')

say_hello()

到这里我们就实现了装饰器效果。

你可能感兴趣的:(Python3)