前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python之装饰器

python之装饰器

作者头像
刘銮奕
发布2019-07-22 17:33:02
4620
发布2019-07-22 17:33:02
举报
文章被收录于专栏:奕知伴解奕知伴解

什么是装饰器?

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

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

什么是闭包函数

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

举例1

代码语言:javascript
复制
def outer():
    a = 1
    def inner():
        print(a)
    inner()
outer()

举例2

代码语言:javascript
复制
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

代码语言:javascript
复制
def func():
    name = 'liu'
    def inner():
        print(name)
    return inner

f = func()
f()

例4

代码语言:javascript
复制
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:闭包函数获取网络应用

代码语言:javascript
复制
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、万能参数

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

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

代码语言:javascript
复制
import time
def func1():
    start = time.time()
    print('in func1')
    print(time.time() - start)

func1()

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

代码语言:javascript
复制
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)

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

代码语言:javascript
复制
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)?这样还是有点麻烦!因为这些函数的函数名可能是不相同,所以引出了语法糖的概念:@装饰器函数名,具体代码用法如下:

代码语言:javascript
复制
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()是一个被装饰的函数。

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

带参数的函数

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

代码语言:javascript
复制
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几乎可以解决所有参数问题。上面的装饰器已经非常完美了,但是有我们正常情况下查看函数信息的方法在此处都会失效:

代码语言:javascript
复制
def index():
    '''这是一个主页信息'''
    print('from index')

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

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

代码语言:javascript
复制
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__)

装饰器的固定格式

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

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

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

装饰器原则

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

习题练习

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

代码语言:javascript
复制
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.编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件

代码语言:javascript
复制
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),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中

代码语言:javascript
复制
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)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 奕知伴解 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是闭包函数
  • 闭包函数最常用的用法
  • 装饰器形成的过程
  • 语法糖
  • 装饰器的作用
  • 带参数的函数
  • 装饰器的固定格式
  • 装饰器原则
  • 习题练习
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档