zoukankan      html  css  js  c++  java
  • python学习日记(装饰器的补充)

    如何返回被装饰函数的函数名及注释?

    问题及实现

    先看典型的装饰器:

    def wrapper(f):#装饰器函数,f是被装饰函数
        def inner(*args,**kwargs):
            '''执行函数之前要做的'''
            r = f(*args,**kwargs)
            '''执行函数之后要做的'''
            return r
        return inner
    @wrapper
    def f():
        '''被装饰函数的注释内容'''
        return 'F'
    f()
    print('注释内容:',f.__doc__)#返回函数注释
    print('函数名:',f.__name__)#返回函数名

    由以上结果可以看出,返回的注释以及函数名是inner函数的,不是被装饰函数的。

    如何解决呢?

    我们只要导入相应模块中的一个方法,也是个装饰器(带有参数的装饰器),来装饰inner函数就可以了。

    from functools import wraps#导入模块中指定的方法、类、属性
    def wrapper(f):#装饰器函数,f是被装饰函数
        @wraps(f)#装饰器,装饰inner函数
        def inner(*args,**kwargs):
            '''执行函数之前要做的'''
            r = f(*args,**kwargs)
            '''执行函数之后要做的'''
            return r
        return inner
    @wrapper
    def f():
        '''被装饰函数的注释内容'''
        return 'F'
    f()
    print('注释内容:',f.__doc__)#返回函数注释
    print('函数名:',f.__name__)#返回函数名

    哒哒~

    一个小练习

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

    from functools import wraps
    import time
    def log(func_input):
        @wraps(func_input)
        def inner(*args,**kwargs):
            ret = func_input(*args,**kwargs)
            log_time = time.asctime()
            with open('log','a',encoding='utf-8') as f:
                # f.write('访问时间:'+log_time+'访问函数:'+func_input.__name__+'
    ')
                f.write('访问时间:{} 访问函数:{}{}'.format(log_time,func_input.__name__,'
    '))#个人觉得格式化更好用
            return ret
        return inner
    @log
    def func1():
        print(111)
    func1()
    time.sleep(2)
    @log
    def func2():
        print(222)
    func2()

    带参数的装饰器

     前面我们有做过计算函数运行时间的装饰器,同时能够处理N个函数,但是当我们不需要的时候,一个一个取消是不是就太麻烦了,而且就算你这样取消了,下次再用的时候,难道还要一个一个再重新调用装饰器吗?一来一回不仅浪费太多时间不说,还有可能会出错,所以,如何解决?

    import time
    def run_time(f):
        def inner():
            start = time.time()
            func_retrun = f()
            end = time.time()
            print('被装饰函数运行时间为:',end - start)
            return func_retrun
        return inner
    @run_time
    def func1():
        time.sleep(0.1)
        print('hello world')
    func1()
    @run_time
    def func2():
        time.sleep(0.1)
        print('hello world')
    func2()

    如果函数过多,要实现一个一个取消,太不切实际了。

    由此,我们引入带参数的装饰器。

    import time
    FLAG = True
    def time_stop(flag):
        def run_time(f):
            def inner():
                #if flag:#形参,接收参数,接收的也就是调用函数time_stop时传的变量FLAG
                if FLAG:#实参,直接是True or False,二者都能实现,但原理不同
                    start = time.time()
                    func_retrun = f()
                    end = time.time()
                    print('被装饰函数运行时间为:',end - start)
                    return func_retrun
                else:
                    func_retrun = f()
                    return func_retrun
            return inner
        return run_time
    
    @time_stop(FLAG)
    def func1():
        time.sleep(0.1)
        print('hello world')
    
    @time_stop(FLAG)
    def func2():
        time.sleep(0.1)
        print('I love this world')
    FLAG = False
    func1()
    func2()

    流程控制语句判断当前参数,若为真,则执行装饰器功能,反之,不执行。

    注意:@time_stop(FLAG)  先执行调用,调用后返回的是run_time 函数(函数内部一切都还未执行),再执行前面的@符号,就变成了@run_time,也就是我们最常见的语法糖。接下来就好理解了,@run_time ----->> func1 = run_time(func1), func2 = run_time(func2)。

    所以以后想要关闭此装饰器,只要修改FLAG参数就可以了。

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

    def wrapper1(f1):#2,f1-->func
        def inner1():
            print('wrapper 1,before')#8,打印
            f1()#9,此时,f1的实参为 func,func()为调用函数
            print('wrapper 1,after')#11,打印,结束后返回到被调用处7
        return inner1
    
    def wrapper2(f2):#4,f2-->inner1
        def inner2():
            print('wrapper 2,before')#6,打印
            f2()#7,此时,f2的实参为 inner1,inner1()为调用函数
            print('wrapper 2,after')#12,打印,结束后返回到被调用处5,到此,程序结束。
        return inner2
    
    @wrapper2 #3,func = wrapper2(func) = wrapper2(inner1),传参f2 = inner1 ,返回inner2,赋值func = inner2,
    @wrapper1 #1,func = wrapper1(func) 传参f1 = func,返回inner1,赋值func = inner1
    def func():
        print('one piece')#10,打印,结束后返回到被调用处9
    func()#5,调用函数,最近一次的赋值func = inner2,即func() = inner2(),调用

    当然,还有三个装饰器的情况出现,原理亦然,不再赘述。

    装饰器练习题

    下载网页内容

    1.编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果

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

    2.为题目1编写装饰器,实现缓存网页内容的功能:
    具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中

    import os
    from urllib.request import urlopen
    def get_url(func):
        def inner(*args,**kwargs):
            if os.path.getsize('url_cache'):#不为0,就为True.必须先创建文件,不然引发异常,因为先直接判断文件的内容,而不是判断文件是否存在
                with open('url_cache','rb') as f:#获取的信息是bytes类型
                    return f.read()
            ret = func(*args,**kwargs)  #get()
            with open('url_cache','wb') as f:#获取的信息是bytes类型
                f.write(b'"New catch_info is:"'+ret)#获取的信息是bytes类型,bytes 是ASCII,不能含有中文
            return ret
        return inner
    @get_url
    def get(url):
        code = urlopen(url).read()
        return code
    sc = get('https://www.baidu.com')
    print(sc)
    
    sc = get('https://www.baidu.com')
    print(sc)

    调用函数记录函数名

    即为上面的 “一个小练习” 

    多个函数认证功能

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

    flag = False
    def login(f):
        def inner(*args,**kwargs):
            global flag#放在使用变量的这一场,放到上一层引发异常
            if flag:
                ret = f(*args, **kwargs)
                return ret
            username = input('username:')
            password = input('password:')
            if username == 'root' and password == '123':
                print('登录成功')
                flag = True
                ret = f(*args,**kwargs)
                return ret
            else:
                print('登录失败')
        return inner
    @login
    def func1():
        print('11111')
    func1()
    @login
    def func2():
        print('22222')
    func2()
    @login
    def func3():
        print('33333')
    func3()

    当然,可根据需求或者个人喜好来相应改变,比如增加三次机会之类的。

    pass

    作者:Gruffalo
    ---------------------------------------------
    天行健,君子以自强不息
    地势坤,君子以厚德载物
    内容仅为自己做日常记录,备忘笔记等
    真小白,努力学习ing...一起加油吧!(ง •̀_•́)ง
  • 相关阅读:
    iOS 整理面试题(上)
    2021年十大白马名单
    RabbitMQ:消息重复消费
    RabbitMQ:保证消息的顺序性
    RabbitMQ:保证消息的可靠性传输
    AWS S3 大文件分片上传
    rebase 用法小结
    Flask at scale
    MySQL分区
    动态规划示例题
  • 原文地址:https://www.cnblogs.com/smallfoot/p/10035199.html
Copyright © 2011-2022 走看看