Python入门自学进阶——1--装饰器

Posted kaoa000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶——1--装饰器相关的知识,希望对你有一定的参考价值。

理解装饰器,先要理解函数和高阶函数。

首先要明白,函数名就是一个变量,如下图,定义一个变量名和定义一个函数,函数名与变量名是等价的。

 既然函数名就是一个变量名,那么在定义函数时,函数的参数就可以是函数名,即可以传递一个函数给另一个函数。同样,函数可以返回一个变量,如return x,那么,这个x既可以是一个变量名,也就可以是一个函数名。

 对于foo1,返回的是inner这个函数对象,如果是return inner(),那就是返回8,函数名(),即函数名加上括号,就是对函数对象的调用,返回的是调用对象的返回值。

当函数的参数中包含函数名,或者作为返回值返回时,这个函数就叫做高阶函数,如上面的foo函数和foo1函数。

其次要理解变量的作用域,即LEGB

L——Local,局部变量,E——Enclosing,嵌套的父级函数局部变量,G——Global,全局变量,B——Build-in,系统模块固定的变量。

变量的查找顺序是L——>E——>G——>B.

第三要理解闭包的概念。

 对于上面的函数,外层outer()函数内定义了一个内部函数inner(),outer返回的是inner,对于返回的inner函数来说,按照前面的作用域LEGB原则,f,即返回的inner函数是无法访问outer函数的局部变量x的,但是,这种写法inner就能访问,这叫做闭包。即返回的内部函数能够访问一个外部变量。闭包=函数块+定义函数时的环境

装饰器的引入

有很多开发完毕的函数,已经被大量使用,如有一个函数:

def foo():
    print('foo func...')

现在想给这个函数加一个功能,计算这个函数的运行时间,一个办法是直接修改foo函数:

import time
def foo():
    start = time.time()
    print('foo func...')
    end = timt.time()
    print('执行时间 %s' % (end-start))

这种方法虽然实现了功能,但是对于已经发布大量使用的函数,是不能这样的修改的。一个原则:开放封闭原则,对修改源代码封闭,对在不修改源代码的前提下对扩展开放。因为原函数已经大量在生产环境应用,直接修改源代码增加功能,代价太大,无法承受。另一方面,如果多个函数都要增加这个功能,如还有一个函数bar,也要增加一个计算运行时间的功能,按上面的写法;

import time
def bar():
    start = time.time()
    print('bar func...')
    end = timt.time()
    print('执行时间 %s' % (end-start))

代码会大量重复。解决重复的方法,就是重复的代码再写成一个函数。

def showtime():
    start = time.time()
    foo()
    end = timt.time()
    print('执行时间 %s' % (end-start))

这样,就实现了功能。但是这只能测试一个foo函数,要测试任意函数,修改如下:

def showtime(func):
    start = time.time()
    func()
    end = timt.time()
    print('执行时间 %s' % (end-start))

按照前面的高阶函数,函数名可以作为参数传递,将showtime的参数设为函数名,想测试哪个函数,就将哪个函数名传进去。

现在功能是实现了,但是我们原来的本意是对原函数扩展功能,上面的实现方法,实际上是新定义了一个函数,其他调用原函数的程序要想使用这个功能,需要大量修改程序,这就改变了函数的调用方式。我们想要的是函数名不变而功能实现,即调用方式不能变。

现在的需求就是我的调用还是foo(),但实现的是showtime(foo)这个功能。

将showtime函数进行修改:

def showtime(func):
    def inner():
        start = time.time()
        func()
        end = timt.time()
        print('执行时间 %s' % (end-start))
    return inner

foo = showtime(foo)  #返回的是inner函数的引用。
foo()   #这其实就是执行inner()函数,inner函数是一个闭包函数,所以它可以在脱离showtime函数后,依然能够使用外部的变量,这里就是func这个参数变量。

要特别注意的是理解:foo = showtime(foo),赋值运算是先计算等号右边的式子,在进行赋值,所以先运行showtime(foo),这个foo是原函数,即这个foo是原函数的引用。赋值操作发生后,foo变成了inner的引用。这里有些混乱的是inner中的func(),字面上还应该是foo,指向的源foo函数的引用,调用的foo(),这个foo是inner。

这里showtime函数就是一个装饰器函数。

如果要扩展其他函数功能,如bar函数,只要:

bar = showtime(bar)
bar()

源函数没有改变,调用方式也没有改变。

python对装饰器的解决方法:

python将bar=showtime(bar)改为在函数上方加上@showtime来解决:

@showtime
def bar():
    print('bar func...')

 再研究一下bar=showtime(bar):

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

函数对象有一个__name__属性,可以拿到函数的名字:

经过装饰器装饰后的函数,实际是函数名与源函数名相同,但是__name__是一个装饰器中的闭包函数,这里是inner。 

对于装饰器,使用的是@showtime,相当于bar=showtime(bar),showtime是有参数的,这个参数f实际传入的就是紧跟@showtime后的def定义的函数名。

如果我们要装饰的函数带有参数,如:

def myadd(a,b):
    print(a+b)

或者一个参数不固定的函数

def myadd1(*a,**b)

对于第一种,需要修改inner的参数:

第二种:

以上是对原函数,即功能函数加参数,同时要在inner中增加相应的参数。

新需求,现在在计算运行时间的同时,还要进行日志操作。但是只是对部分函数要求进行日志操作。

上面的方法不行,因为所有的函数加装饰器后都运行了loger,想要针对不同函数运行不同的装饰功能,需要加一个参数进行控制,是否可以在showtime上加呢?如下:

def showtime(f,flag),这是不可以的,因为装饰器的使用是是固定的@showtime,不带参数的,修改showtime如下:

 

 

这里关键是理解@log('T'),这一句是要先执行log(‘T’),log返回的是showtime,最后就变成了@showtime,此时与前面不同的是,showtime中代入了flag参数,又是一个闭包。 

 运行结果:

 

 

以上是关于Python入门自学进阶——1--装饰器的主要内容,如果未能解决你的问题,请参考以下文章

Python入门自学进阶-Web框架——10Django-应用COOKIESESSION和装饰器,及FBVCBV概念

面向对象编程-进阶(python3入门)

Pytho怎样自学?

Python自学之乐-装饰器浅谈

Python进阶装饰器(Decorator)

Python进阶第九篇装饰器