【学习笔记】装饰器_functools &time_面向函数编程_装饰器的应用,普通装饰器、参数装饰器、多级装饰器等

本文深入探讨了Python装饰器的原理和使用,包括基础装饰器、参数装饰器和多级装饰器。装饰器遵循开放封闭原则,用于在不修改原始函数代码的情况下添加新功能。文中通过实例展示了如何创建装饰器来计算函数执行时间、控制功能启用和弃用,以及实现多层装饰器。此外,还介绍了装饰器在用户认证、日志记录和缓存等功能中的应用。
摘要由CSDN通过智能技术生成
装饰器规范:
# 装饰器作用:不改变原始函数的代码,但需要在原始函数的开始和结束添加新的功能,并执行函数。
# 开放封闭原则:对原始函数代码保持封闭不可修改,对原始代码的扩展功能保持开放
# 装饰器的构建逻辑:了解被装饰的函数,定义装饰器,定义内部函数,返回内部函数的结果值,返回内部函数,语法糖,调用装饰器
# 装饰器的运行逻辑:闭包(函数内部引用函数外部变量或函数),返回函数对象,装饰器函数
# 重构函数:修改原始函数内部的代码,容易导致其他调用该函数的模块,全部失效

# 语法糖:@装饰器函数名,放在被装饰的函数头上,作用等同于“func = timmer(func)”,表示给不改变原始函数的情况下,给原始函数新增功能
# 装饰器的参数:被装饰的函数如果增加参数,装饰器函数内部inner和func都需要新增不定长万能参数

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
'''
Author:leo
Date&Time:2021/6/21 15:52
Project:python  
FileName:20210621_装饰器.py
Desc:装饰器
书籍推荐:python核心编程(第三版)
1.基础装饰器
2.参数装饰器
3.多级装饰器
'''

# --基础装饰器--
print("\n" + " 1.0 基础装饰器(开始)".center(100, "*"))
'''
全栈开发(一):P52
# 装饰器作用:不改变原始函数的代码,但需要在原始函数的开始和结束添加新的功能,并执行函数。
# 开放封闭原则:对原始函数代码保持封闭不可修改,对原始代码的扩展功能保持开放
# 装饰器的构建逻辑:了解被装饰的函数,定义装饰器,定义内部函数,返回内部函数的结果值,返回内部函数,语法糖,调用装饰器
# 装饰器的运行逻辑:闭包(函数内部引用函数外部变量或函数),返回函数对象,装饰器函数
# 重构函数:修改原始函数内部的代码,容易导致其他调用该函数的模块,全部失效

# 语法糖:@装饰器函数名,放在被装饰的函数头上,作用等同于“func = timmer(func)”,表示给不改变原始函数的情况下,给原始函数新增功能
# 装饰器的参数:被装饰的函数如果增加参数,装饰器函数内部inner和func都需要新增不定长万能参数
'''
import time
from functools import wraps
def wrapper(func):  # 装饰器函数, f表示被装饰的函数
    '''
    装饰器函数的注释内容:案例-计算函数的执行时间
    :param func: 被装饰的函数对象
    :return: 返回内部函数对象
    '''
    @wraps(func)  # 系统自带的装饰器,加上以后,可以通过__name__和__doc__调用原始的装饰器名称和注释
    def inner(*args, **kwargs):  # 内部函数
        '''
        装饰器内部函数
        :param args: 元组(1,2,3,4)
        :param kwargs: 字段(name=“leo”,age=18)
        :return:返回被装饰的函数的返回值
        '''
        print("1.新增被装饰的函数执行之前,需要做的事情的代码")
        start_time = time.time()
        func_return = func(*args, **kwargs)     # 被装饰的函数
        print("3.新增被装饰的函数执行之后,需要做的事情的代码")
        end_time = time.time()
        print("4.执行装饰器的内部函数:", inner.__name__, start_time-end_time)
        return func_return      # 返回被装饰的函数的返回值
    return inner          # 装饰器返回的内部函数名称,通过内部函数对象来调用,避免fun(fun)导致代码逻辑错乱
# 语法糖:@装饰器函数名,放在被装饰的函数头上,作用等同于“func = timmer(func)”,表示给不改变原始函数的情况下,给原始函数新增功能
@wrapper  # func = wrapper(func),表示把下面的函数以参数的形式传给装饰器,并重新执行新的下面的函数
def func(arg_str):
    '''
    被装饰的函数的注释内容:案例-计算函数的执行时间
    :param arg_str:字符串
    :return: 返回原始函数执行的结果值
    '''
    time.sleep(0.01)
    print(f"2.func:执行带单个参数的被装饰的函数,返回传入的参数:" + arg_str)
    print(f"2-1.func:被装饰器装饰过的函数,装饰器内部加上@wraps(func),返回原始函数名称,不加,则返回装饰器内部的名称innner:" + str(func.__name__))
    print(f"2-2.func:被装饰器装饰过的函数,装饰器内部加上@wraps(func),返回原始函数注释,不加,则返回装饰器内部的注释inner:" + str(func.__doc__))
    return f'5.func:装饰器的执行结果'
@wrapper
def func_1(arg_str1, arg_str2):
    time.sleep(0.01)
    print("func_1:执行带多个参数的装饰的函数:", arg_str1, arg_str2)
    return 'func_1:装饰器的执行结果'
# func = timmer(func)  # 被@timmer代替
ret = func(arg_str="100")  # 等同于:inner_1 = inner("100")
print(ret)
print("基础装饰器(结束)".center(100, "*"))



# --参数装饰器--
print("\n" + " 2.0 参数装饰器(开始)".center(100, "*"))
'''
问题:当给大量函数都加了装饰器,如果要取消装饰器,怎么操作?
解决:
1.正常的解决方案是一个一个注释装饰器
2.优化的解决方案是加一个全局变量,来控制装饰器的启用和弃用
3.最佳的解决方案是装饰器外部再包一层装饰器用来接受装饰器的参数,三层装饰器封装后就可以用参数来控制装饰器的启用和弃用(函数规范中最高内嵌也建议是三层,超过三层容易引起逻辑混乱)
'''
Flag = True  # 通过变量Flag为真,则所有调用当前装饰器的函数都执行新函数代码;如果为假,则执行原始函数代码
# Flag = False
def timmer_out(flag):  # 形参,名称可以随便取
    def timmer(func):
        def inner(*args, **kwargs):
            if flag:  # 用变量控制装饰器的执行,如果True,则执行装饰器;如果False,则不执行装饰器
                start_time = time.time()
                res = func(*args, **kwargs)
                end_time = time.time()
                print(f"执行装饰器:{start_time-end_time}")
                return res
            else:  # 如果False,则不执行装饰器
                res = func(*args, *kwargs)
                return res
        return inner
    return timmer
# 执行顺序一般从右向左看,依次表示:
# 1.函数time_out+参数flag=装饰器最外层返回的对象timmer;
# 2.timmer加上@后,变成@timmer,等价于run_func=timmer(run_func)
@timmer_out(Flag)  # 等价于:timmer = timmer_out(Flag)
def run_func():
    time.sleep(0.01)
    print("执行被装饰的函数:" + run_func.__name__)
    return run_func.__name__
run_func()
print("参数装饰器(结束)".center(100, "*"))


# --多层装饰器--
print("\n" + " 3.0 多层装饰器(开始)".center(100, "*"))
'''
问题:如果又需要记录用户登录状态,又需要记录函数执行时间,就需要用到多层装饰器
重点:执行顺序是从被装饰的函数最近的wrapper_2到最远的wrapper_1,执行逻辑是从函数的外部到内部,再从内部到外部
'''
def wrapper_1(function):  # function --> inner_2
    def inner_1(*args, **kwargs):
        print("开始执行装饰器1:" + inner_1.__name__)
        res = function(*args, **kwargs)  # function -- inner_2
        print("结束执行装饰器1" + inner_1.__name__)
        return res
    return inner_1
def wrapper_2(function):  # function -- > func
    def inner_2(*args, **kwargs):
        print("开始执行装饰器2:" + inner_2.__name__)
        res = function(*args, **kwargs)  # function - func
        print("结束执行装饰器2" + inner_2.__name__)
        return res
    return inner_2
'''
多层装饰器执行顺序:第一个开始(外层)、第二个开始(内层)、被装饰的函数、第二个结束(内层)、第一个结束(外层)
执行结果:
开始执行装饰器1:inner_1
开始执行装饰器2:inner_2
被装饰的函数:func
结束执行装饰器2inner_2
结束执行装饰器1inner_1
'''
@wrapper_1  # 执行顺序2:func = wrapper_1(func)  -- > wrapper_1(inner_2) = inner_1  -- > func = inner_1
@wrapper_2  # 执行顺序1:func = wrapper_2(func)  -- > wrapper_2(func) = inner_2  -- > func = inner_2
def func():
    print("被装饰的函数:func")
    return "被装饰的函数返回值:func"
func()
print("多层装饰器(结束)".center(100, "*"))


class Test():
    def __init__(self):
        self.login_status = False
        self.login_count = 1
    def test_temp_1(self):
        '''
        题目1:编写一个装饰器,为多个函数添加认证功能(用户的账户密码来源于文件)
        要求:用户登录一次,后续的函数无需再次输入用户名和密码
        :return:
        '''
        # --装饰器案例-1--
        print("\n" + "装饰器案例-1.登录(开始)".center(100, "*"))
        def login(func):
            def inner(*args, **kwargs):
                # global login_status, login_count
                while self.login_count < 4:
                    if self.login_status:
                        ret = func(*args, **kwargs)
                        return ret
                    else:
                        username = input(f"请输入姓名({self.login_count}):")
                        password = input(f"请输入密码({self.login_count}):")
                        if username == "admin" and password == "123456":
                            login_status = True
                            ret = func(*args, **kwargs)
                            return ret
                        else:
                            print("登录失败!")
                    self.login_count += 1
            return inner
        @login
        def shoplist_add_1():
            print("添加一个商品")
        @login
        def shoplist_del_1():
            print("删除一个商品")
        shoplist_add_1()
        shoplist_del_1()
        print("装饰器案例-1.登录(结束)".center(100, "*"))

    def test_temp_2(self):
        '''
        题目2:编写一个装饰器,为多个函数添加记录日志功能,要求每次调用函数,都将函数名称写入文件
        :return:
        '''
        # --装饰器案例-2--
        print("\n" + "装饰器案例-2.日志(开始)".center(100, "*"))
        def log(func):
            def inner(*args, **kwargs):
                with open("func_log.txt", "a", encoding="utf-8") as log_file:
                    log_file.write(f"函数名称:{func.__name__}\n")
                ret = func(*args, **kwargs)
                return ret
            return inner
        @log
        def shoplist_add_2():
            print("成功添加一个商品!")
        @log
        def shoplist_del_2():
            print("成功删除一个商品!")
        shoplist_add_2()
        shoplist_del_2()
        print("装饰器案例-2.日志(结束)".center(100, "*"))
    def test_temp_3(self):
        '''
        1.编写一个函数,用户输入一个url,自动下载页面的结果
        2.编写一个装饰器,实现缓存网页内容的功能
        具体:实现下载的也买你存放于文件中,如果文件中有值(文件大小不为0),就优先从文件中读取网页内容,否则,就下载网页内容
        :return: 
        '''
        print("\n" + "装饰器案例-3.缓存(开始)".center(100, "*"))
        from urllib.request import urlopen
        import os
        def cache(func):
            def inner(*args, **kwargs):
                if os.path.getsize(r"D:\Mytest\Mysvn\python\myfiles\my_course\django\1_script\test_txt"):
                    with open("test_txt", "rb") as f:
                        return f.read()
                ret = func(*args, **kwargs)
                with open("test_txt", "wb") as f:
                    f.write(b"****" + ret)  # 加星号表示是下载的网页内容,不加星号表示文件里面的的缓存内容
                return ret
            return inner
        @cache
        def get(url):
            code = urlopen(url).read()[0:100]
            return code
        ret_byte = get("http://www.baidu.com")
        print(ret_byte)
        print("装饰器案例-3.缓存(结束)".center(100, "*"))
if __name__ == '__main__':
    # Test().test_temp_1()
    # Test().test_temp_2()
    # Test().test_temp_3()
    # Test().test_temp_3()
    pass

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值