python入门基础-函数装饰器的理解

1.装饰器

# 知乎某大神是这么比喻装饰器的:内裤可以用来遮羞,但是到了冬天他就没有办法为我们御寒,聪明的人于是发明了长裤,有了长裤后宝宝再也不冷了,
# 装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
#
# 大神是将程序中原本的函数比喻成内裤,而装饰器比喻成了长裤,这样在寒冬里它们结合一起使用就给所有人带来了温暖。
#
# 装饰器本质上是一个python函数,它可以让其它函数在不改动代码的情况下增加额外的功能,并且装饰器的返回值也是一个函数对象。
# 在很多的应用场景中我们都可以用到装饰器,比如:插入日志、性能测试、事务处理等,正因为有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码,并且继续的重用。
# 那python的装饰器可以概括理解为:装饰器的作用就是为已经存在的对象增加格外的功能,并且不改变原函数的调用方式。

2.装饰器的过程

#首先我们看一段代码例子。

def func():
    print('hello word!')

func()  #我们调用函数后,可以正常打印出来 hello Word!

#比如现在我们有了一个新的需求,要在函数中增加打印函数的执行时间,那添加后的代码就是下面这段了。

import time     #加载time模块
def func():
    start = time.time() #声明变量记录函数开始执行的时间
    print('hello word!')
    end = time.time()       #声明变量记录函数结束执行的时间
    result = end  - start   #声明变量用于接收整个函数运行所耗时间
    print(result)       #打印出来函数执行的总体耗时,当前现在函数中没有什么过多的复杂代码,所以基本不存在耗时多少

#对于上面的需求我们已经实现了,但是我们是改变原函数体的结构实现的,这样在正常的开发环境中肯定是不行的,所以代码优化成下面这种。

import time

def timer():
    start = time.time()
    func()
    end = time.time()
    result = start - end
    print(result)

def func():
    print('hello word!')

timer()

#这样的话,我们就在不改变原函数体的结构情况下,增加了一项打印函数执行耗时的功能,难道这就是装饰器了?NO、NO、NO 还请继续往下看

2.1简单的装饰器

#前面我们也在没有更改原函数体的基础上增加了函数执行耗时的打印,那为什么不叫装饰器呢?因为随便没有改变函数体的结构,
# 但是我们改变了函数名的调用,之前由func()去调用就可以打印hello word,现在是调用timer()函数才能打印,所以我们的代码需要更进一步的调整。

import time

def timer(f): #实际f是func函数名
    def inner():
        start = time.time()
        f()   #实际调用的是func()
        end = time.time()
        result = start - end
        print(result)
    return inner  #将inner函数名返回

def func():
    print('hello word!')

func = timer(func)      #此时是 func = inner
func()      #此时实际调用的函数是  inner()

#那上面这行代码就可以称之为一个简单的函数体了,但是也不是很完美,因为真实的环境中肯定存在很多的函数,在大多函数都需增加函数执行耗时功能的时候。
#我们不可能在没有个函数调用前,都提前像 func = timer(func)这种声明,所以我们就要牵出另一个强大的功能“@语法糖
#@符号是装饰器的语法糖,在定义函数的时候使用,可以避免再一次赋值操作,就是说用了@语法糖可以节省上面的func = timer(func)这步操作。

import time

def timer(f):   #参数f 就是 func 或 func2 函数名
    def inner():
        start = time.time()
        f()     #实际调用的是 func 或 func2函数
        end = time.time()
        result = start - end
        print(result)
    return inner    #将inner函数名返回
@timer   #此时是 func = inner
def func():
    print('hello word!')

@timer      #此时是 func2 = inner
def func2():
    print('is func2')

func()
func2()

#上面通过@语法糖节省了func2 = inner这个过程,以后就可以在需要增加打印耗时的函数上面声明语法糖就可以了。
#上面的原函数调用都没有带上实参,那如果原函数带上了不同的实参,上面的方法肯定行不通了,所以我们需要进一步升级,这里又要用一个强大的功能了。
#要用的功能就是 *args 和 **kwargs 两个动态参数,并且要结合在一起使用,这样就可以天下无敌了

import time

def timer(f):   #参数f 就是 func 或 func2 函数名
    def inner(*args,**kwargs):
        start = time.time()
        f(*args,**kwargs)     #实际调用的是 func 或 func2函数
        end = time.time()
        result = start - end
        print(result)
    return inner    #将inner函数名返回
@timer   #此时是 func = inner
def func(a,b):
    print('hello word!',a,b)

@timer      #此时是 func2 = inner
def func2(a,b,c,d):
    print('is func2',a,b,c,d)

func(1,2)
func2(1,2,3,4)

#那上面的这个装饰器基本可以算作完善了

3.开放封闭原则

#函数在什么时候是开放的和什么时候是关闭的呢?听我娓娓道来
#(1)对扩展是开放的
    #为什么要对扩展开放呢?
    #任何一个程序,不可能在设计之初就已经想好了所有功能,而且后面不需要做任何的更新和修改,所以我们必须允许代码的扩展和添加新功能。

#(1)对修改是封闭的
    #为什么要对修改封闭呢?
    #因为我们写一个函数,很有可能影响其他已经在使用这个函数的用户,所以对修改函数是封闭的。

#装饰器完美的遵循了这个开放封闭原则。

4.装饰器的主要功能和固定结构

#(1)装饰器的主要功能
#在不改变函数调用方式的基础上在函数的前后添加功能。

#(2)装饰器的固定格式
def timer(func):    #装饰器函数,func是被装饰的函数名
    def inner(*args,**kwargs):
        '''在被装饰函数之前要做的操作'''
        ret = func(*args,**kwargs)  #调用被装饰的函数
        '''在被装饰函数之后要做的操作'''
        return ret
    return inner

 5.带参数的装饰器

#5.带参数的装饰器
#比如现在我们有个需求,就是对程序中所有引用了@语法糖的函数去除掉,不许之前增加的额外功能了。那我们一个一个的去除掉,是不是又要浪费了很多时间,那如果过了两天领导要求再加上,我估计可以把人整崩溃了。。。
#所以为了解决这个问题,python中有提供了装饰器函数解决这个问题,可以看下下面的例子。
import datetime
flag = True     #这个flag就是装饰器的开关,当等于True表示开启装饰器的功能,当为Falsh的时候关闭装饰器的功能。

def wrapper_out(flag):
    def wrapper(func):
        def inner(*args,**kwargs):
            '''装饰器功能:打印出当前系统本地时间!'''
            if flag:    #判断flag是否为True
                time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')    #调用模块实现获取当前本地时间
                print('当前系统时间是:{}'.format(time))
            ret = func(*args,**kwargs)
            return ret
        return inner
    return wrapper

@wrapper_out(flag)  #可以理解成两步,第一步python执行wrapper_out(flag),第二步是@'第一步的返回值',那也就是执行@wrapper 也是 wrapper(func1)
def func1():
    return 'is func1'
@wrapper_out(flag)  #执行warpper_out(flag) 和 wrapper(func1)
def func2():
    return 'is func2'

print(func1())
print(func2())

#那上面这套代码无论下面有多少个函数,只要用了这种方式,在以后不需要装饰器里面的功能都可以通过修改flag为Falsh关闭。

#如果现在客户有个需求是记录下程序调用函数的时候,需要将函数名记录到本地一个文件中并且记录上调用的时间。
#那我沿用上面的代码做下升级

import datetime
flag = True     #这个flag就是装饰器的开关,当等于True表示开启装饰器的功能,当为Falsh的时候关闭装饰器的功能。

def wrapper_out(flag):
    def wrapper(func):
        def inner(*args,**kwargs):
            '''装饰器功能:打印出当前系统本地时间!'''
            if flag:    #判断flag是否为True
                time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')    #调用模块实现获取当前本地时间
                with open('run.log','a',encoding='utf-8') as open_f1:
                    open_f1.write('程序在{} 调用了{}函数\n'.format(time,func.__name__)) #向log文件内写入内容
            ret = func(*args,**kwargs)
            return ret
        return inner
    return wrapper

@wrapper_out(flag)  #可以理解成两步,第一步python执行wrapper_out(flag),第二步是@'第一步的返回值',那也就是执行@wrapper 也是 wrapper(func1)
def func1():
    return 'is func1'
@wrapper_out(flag)  #执行warpper_out(flag) 和 wrapper(func1)
def func2():
    return 'is func2'

print(func1())
print(func2())
print(func1.__name__)
print(func2.__name__)

#最终日志输出结果如下:
# 程序在2017-12-29 17:36:59 调用了func1函数
# 程序在2017-12-29 17:36:59 调用了func2函数
#但是我最后print两个函数名称的时候,返回的全部是 inner,为什么会这样呢?因为func1 和 func2函数我都加上了语法糖,那这些函数在全局空间指向的实际就是闭包函数inner
#所以再去查看函数名称,返回的都是inner,那实际开发环境中,很有可能在全局中查看函数名称,本质装饰器原则我们可以按照下述代码解决。
import datetime
from functools import wraps #引用functools模块,加载上wraps函数
flag = True     #这个flag就是装饰器的开关,当等于True表示开启装饰器的功能,当为Falsh的时候关闭装饰器的功能。

def wrapper_out(flag):
    def wrapper(func):
        @wraps(func)    #【将wraps函数添加到闭包函数的上面即可,参数就是func,func也就是全局中传递进来的函数名(func1/func2)】
        def inner(*args,**kwargs):
            '''装饰器功能:打印出当前系统本地时间!'''
            if flag:    #判断flag是否为True
                time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')    #调用模块实现获取当前本地时间
                with open('run.log','a',encoding='utf-8') as open_f1:
                    open_f1.write('程序在{} 调用了{}函数\n'.format(time,func.__name__)) #向log文件内写入内容
            ret = func(*args,**kwargs)
            return ret
        return inner
    return wrapper

@wrapper_out(flag)  #可以理解成两步,第一步python执行wrapper_out(flag),第二步是@'第一步的返回值',那也就是执行@wrapper 也是 wrapper(func1)
def func1():
    '''is func1 notes'''
    return 'is func1'
@wrapper_out(flag)  #执行warpper_out(flag) 和 wrapper(func1)
def func2():
    '''is func2 notes'''
    return 'is func2'

print(func1())
print(func2())
print(func1.__name__)
print(func1.__doc__)
print(func2.__name__)
print(func2.__doc__)

#通过上面使用wraps函数后,再次打印函数名称或者函数注释,都能准确得到 dunc1 和 dunc2 函数的信息。

6.多个装饰器

 

#6.多个装饰器
def wrapper1(f):        #顺序1
    def inner1():
        print('start inner1 !!!')
        f()
        print('end inner1 !!!')
    return inner1

def wrapper2(f):        #顺序2
    def inner2():
        print('start inner2 !!!')
        f()
        print('end inner2 !!!')
    return inner2

@wrapper2
@wrapper1
def func1():
    print('is func1')

func1()

#最终输出结果如下
'''
start inner2 !!!
start inner1 !!!
is func1
end inner1 !!!
end inner2 !!!
'''

#可以明显看到一些规律出来
#1 程序首先进入第一个语法糖(顺序是由上至下)装饰器中的闭包函数,执行被装饰函数之前的操作。
#2 再进入第二语法糖装饰器中的闭包函数,执行被装饰函数之前的操作。
#3 进入原函数中,执行函数体
#4 返回第二语法糖装饰器中的闭包函数,执行被装饰函数之后的操作。
#5 返回第一语法糖装饰器中的闭包函数,执行被装饰函数之后的操作。

下图是我整理的代码执行顺序,可以做下参考:

 

posted @ 2017-12-28 19:02  一拳法师  阅读(563)  评论(1编辑  收藏  举报