Python基础—装饰器函数

装饰器函数

1.为什么使用装饰器

想象一下,有一天领导提了一个需求,统计某个函数执行时间,这个时候你要怎么做呢?

你说这好办啊,

import time
# 导入时间模块

def fun():
    time.sleep(2)
    print('from fun')

def timer(fun):
    start_time = time.time()
    fun()
    stop_time = time.time()
    print(f'函数的运行时间是{stop_time - start_time}')

timer()
   

三下五除二,你就把函数写出来了。

但是接着需求又来了,有一万个函数都要计算运行时间,怎么办呢,这时候你是不是傻眼呢?

2.装饰器初识

概念

本质就是函数(器),能够为其他函数添加附加功能的函数。

装饰器 = 高阶函数 + 函数嵌套 + 闭包

装饰器遵守原则:开放封闭原则

  1. 不修改被修饰函数的源代码
  2. 不修改呗装饰函数的调用方式
def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(f'函数的运行时间是{stop_time - start_time}')
        return res
    return wrapper

@timmer  # 相当于calc = timmer(calc)
def calc(n):
    res = 0
    for i in range(n):
        time.sleep(0.02)
        res += i
    return res

calc(100)

3.装饰器的形成过程:理解装饰器本质

还是回到之前的计算函数运行时间上:首先我们要计算函数的运行时间,重新定义一个计算时间的函数,将原函数放入计时函数中算运行时间,这就是高阶函数的知识。我们回忆一下高阶函数:

高阶函数: 
  1.函数接收的参数是一个函数
  2.函数的返回值是一个函数
  3.满足任意一个条件,就可以称之为高阶函数。
高阶函数知识

装饰器推导过程

第一步:把函数当做参数传入

'''
    将函数当参数传入,计时功能实现了,但是函数的调用方式改变了
'''
def foo():
    time.sleep(2)
    print('hello word')

def test(func):
    print(func)
    start_time = time.time()
    func()  # 在函数另个一函数调用函数,解决不修改原函数代码的功能
    stop_time = time.time()
    print(f'函数运行时间是{stop_time-start_time}')

test(foo)

我们写完上述代码,计时功能是实现了,但是仔细看发现函数的调用方式改变了,怎么办呢?我们可以利用高阶函数中,将函数当参数返回来,看下面代码

第二步:将函数返回后重新赋值

def timer(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print(f'函数的运行时间是{stop_time - start_time}')
    return func

foo = timer(foo)  # 将函数返回重新赋值给foo,解决不修改函数的调用方式。
foo()  # 函数foo多运行了一次。此时高阶函数没法解决这个问题

此时,函数调用方式没有改变,这一点我们做到了,运行结果。。。为什么foo运行了两次呢,仔细看代码发现在运行foo=timer(foo)时,我是将foo返回又重新赋值给foo,但是在调用timer的时候,foo已经运行了一次了,在调用foo()时,foo当然就运行了两次了。此时明显高阶函数已经无法解决不了问题,接下来要是用函数嵌套和闭包的概念解决了,老规矩,让我们回忆一下函数嵌套和闭包函数的知识:

函数嵌套:
  1.函数定义内定义子函数
  2.局部作用域。
    
闭包函数:嵌套函数中的内部函数有对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数.

第三步:利用闭包函数,解决重复运行问题

将函数封装在wrapper中返回,避免在函数体内执行。

def timmer(func):
    def wrapper():
        print(func)
        start_time = time.time()
        func()
        '''
        运行test,此时是把函数封装在闭包函数wrapper()中,
        闭包函数wrapper使用上级作用域传进来的func参数。
        解决了函数func多次调用的问题。
        '''
        stop_time = time.time()
        print('运行时间是%s'%(stop_time-start_time))
    return wrapper


def foo():
    time.sleep(3)
    print('foo函数运行完毕')

foo = timer(foo)
# 返回wrapper的地址,将其wrapper函数赋值给foo函数
test()

这一步,我们将foo作为参数传入,并封装在wrapper中,在wrapper中计算foo的运行时间,利用了闭包的原理,将wrapper返回出去赋值给foo,避免了foo两次调用,同时也解决了改变调用方式的问题,这个就是装饰器的雏形。

第四步:提炼赋值表达式,定义语法糖

到这里完美了吗?并不是,在python中,为了避免每次都要调用赋值,为我们提供了赋值步骤的缩写方式,这就是语法糖@的作用:

语法糖@:

@装饰器函数名 <==> <被装饰函数> = <装饰器函数>(<被装饰函数>)

在上面应用就是:@timer 等效于 foo = timer(foo)

这时候我们就完成了装饰器最基本的框架

def timer(func):
    def wrapper():
        print(func)
        start_time = time.time()
        func()
        stop_time = time.time()
        print('运行时间是%s'%(stop_time-start_time))
    return wrapper

@timmer  # 等效于 test=timmer(test)

def foo():
    time.sleep(3)
    print('test函数运行完毕')

foo()

 还有最后一个问题要解决,刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?

def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        stop_time = time.time()
        print('运行时间是%s'%(stop_time-start_time))
    return inner

@timer
def foo(a):
    print(a)

foo(1)

装饰器——带参数的装饰器
装饰器——带参数的装饰器

现在参数的问题已经完美的解决了,可是如果你的函数是有返回值的呢?

def timer(func):
    def inner(a):
        start = time.time()
        res = func(a)
        stop_time = time.time()
        print('运行时间是%s'%(stop_time-start_time))
        return res
    return inner

@timer
def foo(a):
    print(a)

foo(1)
print(foo(1))
带返回值的装饰器

其实装饰带参的函数并不是什么难事,但假如你有两个函数,需要传递的参数不一样呢?

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> foo1 = timer(foo1)
def foo1(a,b):
    print('in foo1')

@timer   #==> foo2 = timer(foo2)
def foo2(*args,**kwargs):
    print(args,kwargs)
    return 'foo2 over'

foo1(1,2)
foo2(1,2,c=3,d=4)
装饰器——成功hold住所有函数传参

给装饰器加上参数功能
真实的登录系统,账号信息是从数据库提取,不是提前定好的,每个功能函数也有相应的数据库。
这时候需要在装饰器外层在套一层函数,接受不同参数,进入不同的数据库提取数据。

def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('''执行函数之前要做的''')
            re = func(*args,**kwargs)
            if flag:
                print('''执行函数之后要做的''')
            return re
        return inner
    return timer

@outer(False)
def func():
    print(111)

func()

带参数的装饰器
带参数的装饰器
username_list = [
    {'name': 'alex', 'passwd': '123456'},
    {'name': 'linhaifeng', 'passwd': '1234567'},
    {'name': 'wupeiqi', 'passwd': '12345678'}
]

current_info = {'username': None, 'login': False}

def auth(auth_type='filedb'):
    def auth_func(func):
        def wrapper(*args,**kwargs):
            print('认证类型是%s'%auth_type)
            if auth_type == 'filedb':
                if current_info['username'] and current_info['login']:
                    res = func(*args,**kwargs)
                    return res
                username = input('请输入账号:')
                passwd = input('请输入密码:')

                for user_info in username_list:
                    if username == user_info['name'] and passwd == user_info['passwd']:
                        current_info['username'] = username
                        current_info['login'] = True
                        res = func(*args,**kwargs)
                        return res
                else:
                    print('账号或密码错误!')
            elif auth_type == 'ldap':
                print('鬼才他么会玩')
                res = func(*args, **kwargs)
                return res
            else:
                print('鬼知道你用的什么认证方式')
                res = func(*args, **kwargs)
                return res

        return wrapper
    return auth_func


'''
直接运行auth(auth_type='filedb')返回auth_func,在对auth_func使用@语法糖
'''

@auth(auth_type='filedb')
# auth_func =auth(auth_type='filedb') ---> @auth_func 附加了一个auth_type

def index():
    print('欢迎来到京东主页')

@auth(auth_type='ldap')
def home(name):
    print(f'欢迎{name}回家')

@auth(auth_type='sssss')
def shopping_car(name):
    print(f'{name}的购物车中有["奶茶","妹妹"]')


index()
home('ryxiong')
shopping_car('ryxiong')
装饰器加上参数功能

刚刚那个装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效

def index():
    '''这是一个主页信息'''
    print('from index')

print(index.__doc__)    #查看函数注释的方法
print(index.__name__)   #查看函数名的方法
没有wraps
from functools import wraps

def deco(func):
    @wraps(func) #加在最内层函数正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@deco
def index():
    '''哈哈哈哈'''
    print('from index')

print(index.__doc__)
print(index.__name__)

装饰器——wraps demo
有wraps的装饰器

4.开放封闭原则

  • 对扩展是开放的

为什么要对扩展开放呢?

我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。

  • 对修改是封闭的

为什么要对修改封闭呢?

就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。

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

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

1.在不改变函数调用方式的基础上在函数的前、后添加功能

2.固定结构

def timer(func):
    def inner(*args,**kwargs):
        '''执行函数之前要做的'''
        re = func(*args,**kwargs)
        '''执行函数之后要做的'''
        return re
    return inner

装饰器的固定格式
标准版
from functools import wraps

def deco(func):
    @wraps(func) #加在最内层函数正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

装饰器的固定格式——wraps版
带wraps版

6.多个装饰器装饰同一个函数

有些时候,我们也会用到多个装饰器装饰同一个函数的情况。

'''
多层装饰器的套用:
    @deco1
    @deco2
    @deco3
    func()

加载时:
    从上往下依次加载
执行时:
    从下往上依次执行

最开始:
func = deco3(func)

然后:
func = deco2(func),此时func实际上是deco3(func)
所以==> func = deco2(deco3(func))

最后:
func = deco1(func),此时func实际是deco2(deco3(func))
所以==> func = deco1(deco2(deco3(func)))

'''
def wrapper1(func):
    def inner():
        print('wrapper1 ,before func')
        func()
        print('wrapper1 ,after func')
    return inner

def wrapper2(func):
    def inner():
        print('wrapper2 ,before func')
        func()
        print('wrapper2 ,after func')
    return inner

@wrapper2
@wrapper1
def f():
    print('in f')

f()

多个装饰器装饰同一个函数
多个装饰器套用

多层装饰器嵌套问题

 多层装饰器嵌套,靠近被装饰函数先被装饰。逐级往外装饰,外层装饰器装饰的是里层装饰器返回函数。装饰完后逐层向内执行。

 

posted @ 2019-03-22 09:25  ryxiong728  阅读(283)  评论(0编辑  收藏  举报