python---装饰器进阶之路

2023-11-13

装饰器的本质
在不改变被装饰对象原有的’调用方式’和’内部代码’
的情况下给被装饰对象添加新的功能

装饰器的原则
对扩展开放
对修改封闭

为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。

对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。

如何实现装饰器——无参装饰器
需求:统计 index 函数的运行时间,遵循 ‘开放封闭原则’

import time
def index():
    print('from index')
    time.sleep(2)

方案一:该方案改变了源代码

import time
def index():
		start = time.time( )
  	
    print('from index')
    time.sleep(2)
    
    end = time.time( )
		print(f'run time is {end-start}')
    
index()

方案二:遵循了’开放封闭原则’,但是装饰的功能要重复编写,代码冗余

import time
def index():
    print('from index')
    time.sleep(2)

start = time.time( )
index()
end = time.time( )
print(f'run time is {end-start}')

方案三:解决了代码冗余问题,但是功能写死了,只能装饰 index( ),同时对于被装饰的 index 函数的调用方式改变

import time
def index():
    print('from index')
    time.sleep(2)

def wrapper():
  start = time.time( )
  index()
  end = time.time( )
  print(f'run time is {end-start}')

wrapper()
wrapper()
wrapper()

方案四:装饰功能写活,但是对于原函数 index 的调用方式改变了

import time
def index():
    print('from index')
    time.sleep(2)
" wrapper 就是对 index 的装饰"
def wrapper(func):
  start = time.time( )
  func()
  end = time.time( )
  print(f'run time is {end-start}')

wrapper(index)
wrapper(index)
wrapper(index)

方案五:

import time
def index():
    print('from index')
    time.sleep(2)

def outter(func):
  def wrapper():
    start = time.time( )
    func()
    end = time.time( )
    print(f'run time is {end-start}')
    
  return wrapper

index = outter(index) 
"""
调用了 outter ,返回了 wrapper 这个函数对象,再赋值给了 变量名 index 
实际上 index 的调用就是 调用 wrapper
调用 wrapper 就是实现对 func 函数调用的同时,进行装饰,增加了统计运行时间的功能
而 func 是需要被传入的参数,真正传入的参数就是 index 
所以最终实现了 在对 index 函数调用的同时,又添加了新的功能,同时遵循了开放封闭原则
"""
index()

综上,实现了对 index 的装饰,但是,如果换一个需要传入参数的函数,同样增加这个功能,就无法执行

def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

def outter(func):
  def wrapper():
    start = time.time( )
    func()
    end = time.time( )
    print(f'run time is {end-start}')
    
  return wrapper    
    
home = outter(home)
home('jason')
"""
抛出异常:
TypeError: wrapper() takes 0 positional arguments but 1 was given 
此时,由于 原始的 home 函数需要参数,而伪装的 home 函数 (其实就是 wrapper 函数) 没有位置形参来接受
"""

那么,就给伪装的 home 函数 (其实就是 wrapper 函数)增加一个位置形参来接收

def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

def outter(func):
  def wrapper(name):
    start = time.time( )
    func(name)
    end = time.time( )
    print(f'run time is {end-start}')
    
  return wrapper    
    
home = outter(home)
home('jason')

修改过后,home 函数能够运行了,但是对于原来的 index 来说,又多传入了一个参数

import time
def index():
    print('from index')
    time.sleep(2)

def home(name):
	  time.sleep(5)
    print('Welcome to the home page',name)

"借助 *args,**kwargs ,来解决传入的参数问题。"
def outter(func):
  def wrapper(*args,**kwargs):
    start = time.time( )
    func(*args,**kwargs)
    end = time.time( )
    print(f'run time is {end-start}')
    
  return wrapper  

home = outter(home)
home('jason')

index = outter(index) 
index()

对于 index 函数和 home 函数都是没有返回值的函数,如果被装饰者含有返回值,应当如何解决呢?

'调用伪装后的 sleep 函数 就是 调用 wrapper 函数,让伪装后的 sleep 函数有返回值,只要wrapper 有返回值即可,而 wrapper 的返回值必须是内部接受到的 func 调用后的返回值'
def sleep(time):
    time.sleep(3)
    return 3
    
def outter(func):
  def wrapper(*args,**kwargs):
    start = time.time( )
    
    res = func(*args,**kwargs) # 修改处,用 res 接受到了 func 调用后的返回值
    
    end = time.time( )
    print(f'run time is {end-start}')
    
    return res # 修改处, wrapper 函数返回值 res,就是 func 调用后的返回值
  
  return wrapper 

sleep = outter(sleep)
sleep()

装饰器的语法糖
对于被装饰的函数 (index, home, sleep ) 都需要一行代码,对 被装饰的原函数进行伪装

sleep = outter(sleep)
home = outter(home)
index = outter(index) 

为了简化,引入了一种语法,首先将装饰器函数整体放在 被装饰函数的上方,然后在被装饰函数 定义的上方加上**@装饰器函数名**

def outter(func):
  def wrapper(*args,**kwargs):
    start = time.time( )
    res = func(*args,**kwargs) 
    end = time.time( )
    print(f'run time is {end-start}')
    return res 
  return wrapper 

@outter # 相当于 index = outter(index) 
def index():
    print('from index')
    time.sleep(2)

@outter
def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

@outter
def sleep(time):
    time.sleep(3)
    return 3

叠加多个装饰器
装饰器模板: 相对于 原函数 func ,这个模板相当于什么装饰都没有,但是功能可以随时添加

def demo(func):
  	def wrapper(*args,**kwargs):
      res = func(*args,**kwargs)
      return res
    return wrapper

登录认证功能装饰器,在模板上稍作修改

"登录前校验用户名和密码,校验成功才 调用登录函数 "
def auth(func):
  def wrapper(*args,**kwargs):
    inp_name = input('username:  ').strip()
    inp_pwd = input('password:  ').strip()
    if inp_name == 'franklin' and inp_pwd =='123':
      res = func(*args,**kwargs)
      return res
    else:
      print('login failed')
  return wrapper

@auth # change_info = outter(change_info) = wrapper
def change_info(): # 修改用户信息的函数
  print('from change_info')

叠加多个装饰器

def demo_1(func_1):    
  def wrapper_1(*args,**kwargs): 
    print('----from  func_1----')
    res_1 = func_1(*args,**kwargs)      
    return res_1    
  return wrapper_1

def demo_2(func_2):    
  def wrapper_2(*args,**kwargs): 
    print('----from  func_2----')
    res_2 = func_2(*args,**kwargs)      
    return res_2    
  return wrapper_2

def demo_3(func_3):    
  def wrapper_3(*args,**kwargs):   
    print('----from  func_3----')
    res_3 = func_3(*args,**kwargs)      
    return res_3    
  return wrapper_3

@demo_1
@demo_2
@demo_3   
def index():
  print('----index----')
  return None

index()
print(index())
"""
----from  func_1----
----from  func_2----
----from  func_3----
----index----
None
"""

对于叠加多个装饰器的被装饰函数的分析,应当从被装饰函数头上的第一个装饰器,从下向上分析

"在装饰器和被装饰函数的定义阶段"
@demo_1		'''最后:@demo_1 ==> wrapper_2 = demo_1(wrapper_2), func_1 传入的是 wrapper_2
						demo1(wrapper_2) 返回的是 wrapper_1对象'''
@demo_2		'''其次:@demo_2 ==> wrapper_3 = demo_2(wrapper_3),  func_2 传入的是 wrapper_3
						demo2(wrapper_3) 返回的是 wrapper_2对象'''
@demo_3   '''首先:@demo_3 ==> index = demo_3(index),  func_3 传入的是 原始未index
						demo3(index) 返回的是 wrapper_3对象'''
def index():
  print('----index----')
  return None


所以,叠加多个装饰器之后, index 函数 是被 wrapper_1 函数给伪装了,调用 index 其实是调用 wrapper_1

"在最后的调用阶段"
index() '调用被 伪装后的 index 函数 ===> wrapper_1()'

def demo_1(func_1):  # func_1 传入的是 wrapper_2  
  def wrapper_1(*args,**kwargs): 
    print('----from  func_1----')
    res_1 = func_1(*args,**kwargs)      
    return res_1    
  return wrapper_1  


先打印 ----from func_1---- ,再调用 wrapper_2,将 wrapper_2 的返回值 赋给 res_1, wrapper_1 的返回值 res_1

 '调用被 wrapper_2() '

def demo_2(func_2):   # func_2 传入的是 wrapper_3
  def wrapper_2(*args,**kwargs): 
    print('----from  func_2----')
    res_2 = func_2(*args,**kwargs)      
    return res_2    
  return wrapper_2

先打印 ----from func_2---- ,再调用 wrapper_3,将 wrapper_3 的返回值 赋给 res_2, wrapper_2 的返回值 res_2

 '调用被 wrapper_3 () '

def demo_3(func_3):   # func_3 传入的是 原始未装饰的index
  def wrapper_3(*args,**kwargs):   
    print('----from  func_3----')
    res_3 = func_3(*args,**kwargs)      
    return res_3    
  return wrapper_3

先打印 ----from func_3---- ,再调用 原始index,将 原始index 的返回值 赋给 res_3, wrapper_3 的返回值 res_3

调用 wrapper_1
打印 ----from  func_1----

调用 wrapper_2--> 打印 ----from  func_2---- 
  								再调用 wrapper_3 -->打印 ----from  func_3---- 
  																		再调用 原始index-->|打印 ----index----
    
    																								-->|原始 index 的返回值 None 赋给 res_3
      																							-->|wrapper_3 的返回值 res_3 = None
          
																	wrapper_3 的返回值 None 赋给 res_2  <-----------
          												wrapper_2 的返回值 res_2 = None
              
            wrapper_2 的返回值 None 赋给 res_1  <-----------
            wrapper_1 的返回值 res_1 = None
            
wrapper_1 返回 None <-----------
就是被装饰后的 index 返回 None ,所以 print(index()) 打印出来的是就是 None

总结:按照装饰器从上到下的顺序,依次执行内部的 wrapper 函数,最后执行原始的函数,原始函数的返回值就是被装饰后函数的返回值

@demo_1 ---> 运行 demo_1 中的 wrapper_1, wrapper_1 中需要运行wrapper_2
@demo_2 ---> 再运行 demo_2 中的 wrapper_2,wrapper_2 中需要运行wrapper_3
@demo_3 ---> 然后运行 demo_3 中的 wrapper_3,wrapper_3 中需要运行原始 index
				---> 最后运行 原始 index
"""
----from  func_1----
----from  func_2----
----from  func_3----
----index----
"""

实例:

import time

# 统计运行时间
def timer(func):
  def wrapper(*args,**kwargs):
    start = time.time( )
    res = func(*args,**kwargs) 
    end = time.time( )
    print(f'run time is {end-start}')
    return res 
  return wrapper

# 登录校验装饰器
def auth(func):
  def wrapper(*args,**kwargs):
    inp_name = input('username:  ').strip()
    inp_pwd = input('password:  ').strip()
    if inp_name == 'franklin' and inp_pwd =='123':
      res = func(*args,**kwargs)
      return res
    else:
      print('login failed')
  return wrapper

# 需要被装饰的函数
def index():
  print('----index----')
  return None

当 两个装饰的 位置不同时,运行的结果也是不同的

@auth 
@timer
def index():
  ...

index()

"先登录校验,再运行统计时间的函数,最后运行原始 index, 统计的是 原始 index 的运行时间"

@auth
@timer
def index():
  ...

index()

"先运行统计时间的函数,再登录校验,最后运行原始 index ,统计的是 登录校验 + 原始 index 的运行时间"

有参装饰器
如果有不同的认证方式,如何把认证功能写活?

'用一个 mode 参数来控制不同方式进行认证 , 问题在于 mode 这个参数应该这么传入 '
def auth(func):
  def wrapper(*args,**kwargs):
    inp_name = input('username:  ').strip()
    inp_pwd = input('password:  ').strip()
    # 基于文件
    if mode == 'file':
      if inp_name == 'franklin' and inp_pwd =='123':
        res = func(*args,**kwargs)
        return res
      else:
        print('login failed')
    # 基于mysql
    elif mode == 'MySQL':
      print('基于 mysql 的认证')
    # 基于其他方式
    else:
      print('基于 其他方式 的认证')
    
  return wrapper

首先:def wrapper(mode, *args,**kwargs) 括号内不能添加

其次:def auth(func,mode ) 由于语法糖,也不能添加

所以,由于不能直接添加,考虑再用闭包函数,给 auth 再套一层函数

def outter(mode): # 最外层传入 mode 参数
  def auth(func):
    def wrapper(*args,**kwargs):
      ... # wrapper 内代码 需要用到 mode
    return wrapper
  return auth 

auth = outter('mysql') # 调用 outter 返回的就是 函数对象 auth

@auth # 此时,相当于已经把 mode 传入
def index():
  print('index')

简化: @outter(‘mysql’) -->outter(‘mysql’) 返回的就是 函数对象 auth,所以 = @auth

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python---装饰器进阶之路 的相关文章

随机推荐

  • net core 下的图形验证码

    首先 通过 Nuget 安装 dotnet add package Lazy Captcha Core 注册服务 默认使用了内存存储 AddDistributedMemoryCache builder Services AddCaptcha
  • 什么是IDP?---What Is an Internal Developer Platform (IDP)?

    The modern approach to software delivery is based on cloud native services and the DevOps culture entailing software dev
  • 项目-天气邮局

    一 项目背景 http协议被广泛使用 从移动端 pc端浏览器 http协议无疑是打开互联网应用窗口的重要协议 http在网络应用层中的地位不可撼动 是能准确区分前后台的重要协议 在学习完网络的有关知识后 HTTP服务器无疑是巩固及应用所学知
  • 怎么用linux查看xml文件格式,xml是什么格式?xml文件格式用什么软件可以打开

    xml是什么格式 xml文件是很多用户在电脑上看见过了 很多小伙伴看到了xml格式的文件都不知道这个是什么东东 其实这个xml也是一种比较有用的文件 可以用来存储软件数据 不过不是所有的软件都可以打开的 下面智能手机网就来科普一下xml是什
  • 各种虚拟机体验杂谈 --- 兼发布 google chrome os (chromiumos) vmware版本

    前两天赶时髦 把笔记本换上了win8 pro 换win8pro的原因 一个是价格真的很有诚意 另一个就是从DP版本开始就一直用 虽然兼容性问题多多 但作为宿主主机还行 而且xenclient也实在是让人窝火 号称裸机虚拟 其实硬盘速度慢如蜗
  • 读论文(五)MedDialog【参考性大】【可复现】

    Abstract 医疗对话系统有望帮助远程医疗增加医疗保健服务的可及性 提高患者护理质量并降低医疗成本 为促进医学对话系统的研发 我们构建了大规模的医学对话数据集 MedDialog 其中包含中文数据集340万条医患对话 英文数据集120条
  • 24 个 ES6 方法,解决实际开发的 JS 问题

    1 如何隐藏所有指定的元素 tips 本文主要介绍 24 中 es6 方法 这些方法都挺实用的 本本请记好 时不时翻出来看看 const hide el gt Array from el forEach e gt e style displ
  • 使用 Socket 通信实现 FTP 客户端程序

    转 https www ibm com developerworks cn linux l cn socketftp index html FTP FTP 概述 文件传输协议 FTP 作为网络共享文件的传输协议 在网络应用软件中具有广泛的应
  • python是一门面向过程的语言有哪些,python是面向过程的吗

    python是面向过程的吗 1 面向过程 核心是过程二字 过程指的是解决问题的步骤 好比如设计一条流水线 是一种机械式的思维方式 就是程序从上到下一步步执行 一步步从上到下 从头到尾的解决问题 基本设计思路就是程序一开始是要着手解决一个大的
  • 迷你Web文件服务器

    在开发Web程序的时候 有时候需要一个轻量级的Web服务器 用来响应前端的请求 前端一般的请求可以通过本地文件的方式显示 但是毕竟不是真正的Web服务器 有了这个需求 我们开发了一款迷你绿色通用的Web文件服务器 下载地址 WebServe
  • Ubuntu16.04.7+Qt15.5.0环境配置(一条龙讲解)

    目录 1 下载并安装Ubuntu 2 Qt下载与安装 3 Qt环境配置 4 设置编译套件 5 创建qt快速启动脚本 1 下载并安装Ubuntu Ubuntu16 04 7下载链接https releases ubuntu com xenia
  • ipconfig bash: ipconfig: command not found...

    在使用linux查看端口的时候 应该用ifconfig Windows才使用ipconfig
  • Qt 操作SQLite数据库

    一 SQLite 介绍 Sqlite 数据库作为 Qt 项目开发中经常使用的一个轻量级的数据库 可以说是兼容性相对比较好的数据库之一 Sqlite就像Qt的亲儿子 如同微软兼容Access数据库一样 Qt5 以上版本可以直接使用 Qt自带驱
  • 09字符串排序

    给定两个字符串 从字符串2中找出字符串1中的所有字符 去重并按照ASCII码值从小到大排列 输入字符串1长度不超过1024 字符串2长度不超过100 字符范围满足ASCII编码要求 按照ASCII由小到大排序 输入描述 bach bbaac
  • CBAM:融合通道和空间注意力的注意力模块

    点击上方 AI公园 关注公众号 选择加 星标 或 置顶 作者 Sik Ho Tsang 编译 ronghuaiyang 导读 使用CBAM加持的MobileNetV1 ResNeXt ResNet WRN优于使用SENet的网络 在这篇文章
  • java:统计数组中元素出现的个数

    问题描述 定义一个方法传入一个int类型数组 输出这个数组中每一个数字及其出现的个数 例如 传入数组 1 2 2 2 3 3 4 4 4 4 打印结果 数字1出现了1次 数字2出现了3次 算法思想 这里主要是在实现数组元素的遍历过程中 如果
  • Springboot 整合mybatis-plus +代码生成器

    mybatis plus官方文档 https mp baomidou com guide 新建一个Springboot项目 代码生成结构如下 一 添加依赖
  • el-dialog弹窗改变默认样式,改变弹窗高度位置

    el dialog弹窗改变默认样式 改变弹窗高度位置 在el dialog上添加class view dialog
  • 4https 原理

    春光正暖 情绪微高 穿行于街巷 浮现三两过往事 不惧时光肆意流淌 新的一年 万事顺遂 文章目录 1 http的缺点 2 加密算法 2 1 对称加密 2 2 非对称加密 算法的实现的思路 公钥加密 私钥解密 2 3 ca的诞生 ca如何生成证
  • python---装饰器进阶之路

    装饰器的本质 在不改变被装饰对象原有的 调用方式 和 内部代码 的情况下给被装饰对象添加新的功能 装饰器的原则 对扩展开放 对修改封闭 为何要用装饰器 软件的设计应该遵循开放封闭原则 即对扩展是开放的 而对修改是封闭的 对扩展开放 意味着有