专栏首页奕知伴解python之装饰器

python之装饰器

什么是装饰器?

装饰器本质上就是一个python闭包函数,他可以让其他函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。

装饰器的应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。

什么是闭包函数

内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数。常用的使用方法:嵌套函数 + 内部函数调用外部函数的变量

举例1

def outer():
    a = 1
    def inner():
        print(a)
    inner()
outer()

举例2

def outer():
    a = 1    
    def inner():
        print(a)
    return inner

inn = outer()
inn()

例1中如果去调用outer(),命名空间里就会创建a = 1,再创建inner(),然后a就会消失。每次调用outer()的时候函数outer()里的变量就会创建,结束就会消失,很是浪费资源。但是例2inner函数当做内存地址传到外面,并且外部inn会使用inner,此时 a = 1 就会在内存中一直存在。

闭包函数最常用的用法

例3

def func():
    name = 'liu'
    def inner():
        print(name)
    return inner

f = func()
f()

例4

name = 'liu'
def func2():
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f2 = func2()
f2()

嵌套函数:在例3中有func(),inner() 内部函数调用外部函数的变量:在inner函数之外,func函数之内需要有变量被inner函数调用。如被例3中的name变量。例4中name= 'liu' 是全局变量输出是None,不满足闭包函数的条件。

例5:闭包函数获取网络应用

from urllib.request import urlopen
def get_url():
    url = 'https://www.baidu.com'
    def get():
        ret = urlopen(url).read()
        print(ret)
    return get

inn = get_url()
inn()

装饰器形成的过程

最简单的装饰器 : 1、有返回值的 2、有一个参数 3、万能参数

为什么要使用装饰器呢?下面我们看一下下面的需求:我们需要测试一下每个函数的执行时间。

在测试的函数量比较少的话,我们可以这样的写:

import time
def func1():
    start = time.time()
    print('in func1')
    print(time.time() - start)

func1()

虽然上述的代码解决问题,但是如果我们成百上千的函数量,这样的方法就不太行了。这时你可能会想把计算函数执行的时间写一个函数来执行,如下面代码:

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

def func1():
    print('in func1')


def func2():
    print('in func2')

timer(func1)
timer(func2)

然而这样的工作量只是相对减少了一些,并不满意。那还记得上面讲的闭包函数吗,对的,这里我们要使用到闭包函数来解决做一下尝试看看效果如何。

import time

def func1():
    print('in func1')

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

func1 = timer(func1)
func1()

效果还不错,简便了许多。或许这里你看的不太懂,里面来回的调用,有点被绕晕了。这里先不着急,慢慢往下看:

函数名其实就是内存地址。上图中func就是闭包函数中说到的:内部函数调用外部函数的变量。python是自上而下进行编译,所以执行顺序是执行两个函数:第一步:func1(),第二步:timer(),第三步:执行func的=右边timer函数把func1的内存地址作变量传入timer函数中,第四步执行inner函数,第五步把inner函数的内存地址返回,第六步func1接收到inner的值,第七步执行func1函数,第八步执行start,第九步执行函数func,调用func1函数,接下来依次往下执行。

语法糖

虽然上述代码程序已经很完美了,但是每次都要去执行func1 = timer(func1)?这样还是有点麻烦!因为这些函数的函数名可能是不相同,所以引出了语法糖的概念:@装饰器函数名,具体代码用法如下:

import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> func1 = timer(func1)
def func1():
    print('in func1')


func1()

装饰器的作用

装饰器的作用 —— 不想修改函数的调用方式 但是还想在原来的函数前后添加功能 timmer就是一个装饰器函数,只是对一个函数 有一些装饰作用 func()是一个被装饰的函数。

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

带参数的函数

刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?

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   #==> func1 = timer(func1)
def func1(a,b):
    print('in func1')

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))

函数参数的*args,**kwargs几乎可以解决所有参数问题。上面的装饰器已经非常完美了,但是有我们正常情况下查看函数信息的方法在此处都会失效:

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

print(index.__doc__)    #查看函数注释的方法
print(index.__name__)   #查看函数名的方法

为了不让他们失效,我们还要在装饰器上加上一点来完善它:

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__)

装饰器的固定格式

根据上述多种代码的样例,我们可以得到下面这样的装饰器的基本固定格式。

def wrapper(f):    #装饰器函数,f是被装饰的函数
    def inner(*args,**kwargs):
        '''在被装饰函数之前要做的事'''
        ret = f(*args,**kwargs)    #被装饰的函数
        '''在被装饰函数之后要做的事'''
        return ret
    return inner

@wrapper         #语法糖 @装饰器函数名

装饰器原则

1.对扩展是开放的     为什么要对扩展开放呢?     我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。 2.对修改是封闭的     为什么要对修改封闭呢?     就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。 装饰器完美的遵循了这个开放封闭原则。

习题练习

1.编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件), 要求登录成功一次,后续的函数都无需再输入用户名和密码

FLAG = False

def log(func):
    def inner(*args,**kwargs):
        global FLAG
        if FLAG :
            ret = func(*args,**kwargs)
            return ret
        else:
            username = input('输入用户名:')
            password = input('输入密码:')
            if username == '刘东' and password == '666':
                FLAG = True
                ret = func(*args, **kwargs)
                return ret
            else:
                print('登录失败')
    return inner

@log

def shoplist_add():
    print('增加一件商品')

def shoplist_del():
    print('删除一件商品')

2.编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件

def log(func):
    def inner(*args,**kwargs):
        with open('log','a',encoding='utf-8') as f:
            f.write(func.__name__+'\n')
        ret = func(*args,**kwargs)
        return ret
    return inner

@log
def shoplist_add():
    print('增加一件物品')

@log
def shoplist_del():
    print('删除一件物品')

1.编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果 2.为题目1编写装饰器,实现缓存网页内容的功能: 具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中

import os
from urllib.request import urlopen
def cache(func):
    def inner(*args,**kwargs):
        if os.path.getsize('web_cache'):
            with open('web_cache','rb') as f:
                return f.read()
        ret = func(*args,**kwargs)  #get()
        with open('web_cache','wb') as f:
            f.write(b'*********'+ret)
        return ret
    return inner

@cache
def get(url):
    code = urlopen(url).read()
    return code


 # {'网址':"文件名"}
ret = get('http://www.baidu.com')
print(ret)

本文分享自微信公众号 - 奕知伴解(yzbjchat),作者:刘銮奕

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 思科ISE分布式部署及安装

    刘銮奕
  • Paloalto 系统初始化和管理

    Device > Setup > Management > General Settings

    刘銮奕
  • Nginx重装与平滑升级

    第一步:停止Nginx软件 第二步:删除/usr/local/nginx文件夹 第三步:下载Nginx-1.16重新进行配置、编译以及安装 缺点:在重装升级的过...

    刘銮奕
  • 12步轻松搞定Python装饰器

    原文链接:http://www.cnblogs.com/imshome/p/8327438.html 呵呵!作为一名教python的老师,我发现学生们基本上一...

    CDA数据分析师
  • 12步轻松搞定Python装饰器

    Python里面的装饰器比较复杂,下面12步可以帮你你较好的理解Python中的装饰器 1. 函数 在python中,函数通过 def关键字、函数名和可选的参数...

    YingJoy_
  • Font Awesome 一套绝佳的图标字体库和CSS框架

    王小婷
  • JSONP 的工作原理

    很简单,就是利用<script>标签没有跨域限制的“漏洞”(历史遗迹啊)来达到与第三方通讯的目的。当需要通讯时,本站脚本创建一个<script>元素,地址指向第...

    一个会写诗的程序员
  • 医学假阴性?看看在机器学习中如何用来衡量分类模型的效果(附代码)

    近日来,新冠肺炎核酸检测“假阴性”引起了关注。所谓的假阴性,就是患者是新型冠状病毒感染者,但是核酸没检测出来,报告阴性。有专家分析,任何核酸检测的检出率都不可能...

    数据派THU
  • Applet小应用程序之间的通讯

    1、 首先解压:如何运行applet.zip文件,解压后如图

    张泽旭
  • 8.python之面相对象part.3

    在python这门编程语言中,一个类可以去继承一个父类甚至多个父类,只继承一个父类就是单继承,如果一个子类继承了多个父类,那么这就是多继承。原始类被称为“基类”...

    py3study

扫码关注云+社区

领取腾讯云代金券