当前位置: 首页 > news >正文

Python语法入门之装饰器的基本用法

        本文,我将为大家详细讲解Python中decorator装饰器这一特殊的语法结构。

什么是装饰器

        装饰器(Decorator)是 Python 中一种特殊的语法结构,用于在不修改原函数代码的情况下,动态地扩展或修改函数/类的功能。它的核心思想是通过高阶函数实现功能的"包装"。

函数闭包的基本概念

        函数闭包(Closure)和装饰器(Decorator)在 Python 中密切相关,​装饰器的实现依赖于闭包的特性。所谓闭包就是指函数定义,变量引用等一些常规在函数外进行的操作,全部在一个函数内部实现。比如说:

def outter_func(outter_var):'''inner_func为outter_func的闭包函数'''def inner_func():'''outter_var位于是outter_func函数的变量可以与inner_func函数共享,并且相对于inner_func内部来说相当于全局变量'''inner_var=outter_var+1return inner_varinner_var=inner_func()return outter_var,inner_var
outter_var,inner_var=outter_func(outter_var=2)
print(outter_var,inner_var)

闭包特性

        闭包函数可以访问其外层函数作用域内的所有信息,包括共享和使用该外层函数的变量,无论闭包层数有多深。

        闭包的最大层数由最外层函数内部的def数量决定。

不带参数的装饰器

        装饰器本质上是一个接受函数作为参数,并返回一个新函数(通常是闭包)的高阶函数。装饰器的核心实现正依赖于闭包这一特性。其一般结构为:

基本示例

from functools import wraps
def my_decorator(func):'''wrapper为my_decorator的闭包函数,我们可以将被修饰的函数func包裹在其内部在不改变被修饰函数func的前提下在其运行前后执行某些操作'''@wraps(func)#wraps修饰器def wrapper(word):#wrapper的参数与待修饰函数的参数一致print("函数执行前...")func(word)print("函数执行后...")return wrapper#使用装饰器
@my_decorator
def say_word(word):print(word)#调用被装饰过的函数
say_word('Hello,Decorator!')

运行结果:

 @wraps的作用

        在 Python 中,@wraps装饰器工厂​(来自functools模块),它的作用是保留被装饰函数的元信息​(如 __name____doc__ 等),避免装饰器覆盖原函数的属性。

         这里我们以__name__(函数名称),__doc__(函数文档注释)为例,来看一下使用wraps与不使用wraps有何区别。

from functools import wraps
'''wraps作用是保留被装饰函数的元信息(如__name__、__doc__等)
避免装饰器覆盖原函数的属性。
'''
def my_decorator(func):def wrapper(*args,**kwargs):"""Wrapper docstring"""return func(*args,**kwargs)return wrapperdef my_decorator_with_wraps(func):@wraps(func)#加上这一行可以将被装饰函数的元信息复制到wrapper函数上,使其看起来像原函数def wrapper(*args,**kwargs):"""Greet docstring"""return func(*args,**kwargs)return wrapper'''同一个函数分别被加了@wraps(func)与不加wraps的装饰器修饰
其__name__,与__doc__不一样'''
@my_decorator
def greet():"""Greet docstring"""print("Hello!")
print(greet.__name__)#wrapper
print(greet.__doc__)  @my_decorator_with_wraps
def greet():"""Greet docstring"""print("Hello!")
print(greet.__name__)
print(greet.__doc__)   

 运行结果

带参数的装饰器(装饰器工厂)

     相较于不带参数的装饰器,带参数的装饰器也叫做装饰器工厂,是我们在开发中最常用的一种形式,带参数的装饰器的闭包层数一般而言为2层。

带参数的装饰器(装饰器工厂)的基本结构(闭包层数2层) 

from functools import wraps
def outer_decorator(*args,**kwargs):  #外层装饰器(工厂)def inner_decorator(func): #内层装饰器(真正的装饰器)@wraps(func)def wrapper():#装饰逻辑return func(*args, ​**kwargs)return wrapperreturn inner_decorator

        闭包层数为两层的装饰器,乍一看,看上去是不是很复杂,其实不然,我们对比一下不带参数的装饰器,看一下二者的区别

from functools import wraps
def my_decorator(func):     def wrapper(*args, ​**kwargs): #装饰器逻辑return func(*args, ​**kwargs)return wrapper

        仔细观察不难发现,所谓的装饰器工厂其实就是将不带参数的装饰器(最基础的装饰器)放到了一个函数的内部,也就是又进行了一次数闭包操作(共计两层闭包), 最外层的函数可以传入参数,并且供内部装饰器内的被修饰函数func调用,之所以可以调用是因为:

        闭包函数可以访问其外层函数作用域内的所有信息,包括共享和使用该外层函数的变量,无论闭包层数有多深。

        闭包的最大层数由最外层函数内部的def数量决定。 

比如:这里我写一个双层闭包函数,并且在函数内链式调用。

def outter_func(number:int=1):def inner_func():def innest_func():print(number)innest_func()inner_func()
outter_func()#输出结果:1

结果:

        不难发现,即使是位于最内层的innest_func依然可以调用outter_func的kwargs变量number,这也 进一步印证了我们上边谈到的闭包特性。

        当然,如果不使用@wraps这一操作的话,我们可以将两层闭包变为一层,即将inner_decorator去掉

from functools import wraps
def outer_decorator(*args,**kwargs):  #外层装饰器(工厂)def inner_decorator(func): #内层装饰器(真正的装饰器)@wraps(func)def wrapper():#装饰逻辑return func(*args, ​**kwargs)return wrapperreturn inner_decorator

 不使用@wraps时,且装饰器内的参数不需要传入可以将内层装饰器去掉,此时装饰器为

 def outer_decorator(*args,**kwargs):#装饰器(工厂)def wrapper(func):#装饰逻辑return funcreturn wrapper

基本示例

        flask风格的路由注册,这里使用了带参数的装饰器,但却没有使用两层闭包,因为这个参数不需传入到被修饰函数,如果需要传入则会报错。

#flask风格的路由注册
routes={}
def route(path):  #装饰器工厂def decorator(func):  #真正的装饰器routes[path]=func  #注册函数到路由表return funcreturn decorator@route("/home")#注册到路由表,装饰器的参数为path
def home():return "主页"@route("/about")
def about():return "关于我们"print(routes)  #输出:{'/home': <function home>, '/about': <function about>}

什么时候需要装饰器?

1. 代码复用(避免重复代码)​

场景​:多个函数需要相同的预处理或后处理逻辑(如日志、计时、权限检查)。
问题​:如果每个函数都写一遍相同的代码,会导致冗余,难以维护。

2. 权限校验与访问控制

场景​:某些函数需要登录后才能调用(如 Web 框架的路由)。
问题​:如果每个函数都检查 if not user.is_authenticated,代码会臃肿。

3.修改函数行为(如单例、Deprecation 警告)​

场景​:希望函数只执行一次,或标记某些函数为“已废弃”。

普通函数转换为装饰器的基本思路 

 1.确定复用代码部分

2. 确定修改函数行为

3.将装饰器逻辑部分代码写入到wrapper中,使得被修饰函数在wrapper中被包裹

怎么转换为装饰器 

这里,我们以pywechat内auto_reply_to_group函数修改为装饰器为例,来讲解一下具体操作方法

def auto_reply_to_friend(friend:str,duration:str,content:str,save_chat_history:bool=False,capture_screen:bool=False,folder_path:str=None,search_pages:int=5,wechat_path:str=None,is_maximize:bool=True,close_wechat:bool=True)->(str|None):'''该方法用来实现类似QQ的自动回复某个好友的消息\nArgs:friend:好友或群聊备注\nduration:自动回复持续时长,格式:'s','min','h',单位:s/秒,min/分,h/小时\ncontent:指定的回复内容,比如:自动回复[微信机器人]:您好,我当前不在,请您稍后再试。\nsave_chat_history:是否保存自动回复时留下的聊天记录,若值为True该函数返回值为聊天记录json,否则该函数无返回值。\ncapture_screen:是否保存聊天记录截图,默认值为False不保存。\nsearch_pages:在会话列表中查询查找好友时滚动列表的次数,默认为5,一次可查询5-12人,当search_pages为0时,直接从顶部搜索栏搜索好友信息打开聊天界面\nfolder_path:存放聊天记录截屏图片的文件夹路径\nwechat_path:微信的WeChat.exe文件地址,主要针对未登录情况而言,一般而言不需要传入该参数,因为pywechat会通过查询环境变量,注册表等一些方法\n尽可能地自动找到微信路径,然后实现无论PC微信是否启动都可以实现自动化操作,除非你的微信路径手动修改过,发生了变动的话可能需要\n传入该参数。最后,还是建议加入到环境变量里吧,这样方便一些。加入环境变量可调用set_wechat_as_environ_path函数\nis_maximize:微信界面是否全屏,默认全屏。\nclose_wechat:任务结束后是否关闭微信,默认关闭\nReturns:chat_history:json字符串,格式为:[{'发送人','时间','内容'}],当save_chat_history设置为True时'''if save_chat_history and capture_screen and folder_path:#需要保存自动回复后的聊天记录截图时,可以传入一个自定义文件夹路径,不然保存在运行该函数的代码所在文件夹下#当给定的文件夹路径下的内容不是一个文件夹时if not os.path.isdir(folder_path):#raise NotFolderError(r'给定路径不是文件夹!无法保存聊天记录截图,请重新选择文件夹!')duration=match_duration(duration)#将's','min','h'转换为秒if not duration:#不按照指定的时间格式输入,需要提前中断退出raise TimeNotCorrectError#打开好友的对话框,返回值为编辑消息框和主界面edit_area,main_window=Tools.open_dialog_window(friend=friend,wechat_path=wechat_path,is_maximize=is_maximize,search_pages=search_pages)#需要判断一下是不是公众号voice_call_button=main_window.child_window(**Buttons.VoiceCallButton)video_call_button=main_window.child_window(**Buttons.VideoCallButton)if not voice_call_button.exists():#公众号没有语音聊天按钮main_window.close()raise NotFriendError(f'非正常好友,无法自动回复!')if not video_call_button.exists() and voice_call_button.exists():main_window.close()raise NotFriendError('auto_reply_to_friend只用来自动回复好友,如需自动回复群聊请使用auto_reply_to_group!')########################################################################################################chatList=main_window.child_window(**Main_window.FriendChatList)#聊天界面内存储所有信息的容器initial_last_message=Tools.pull_latest_message(chatList)[0]#刚打开聊天界面时的最后一条消息的listitem   Systemsettings.copy_text_to_windowsclipboard(content)#复制回复内容到剪贴板Systemsettings.open_listening_mode(full_volume=False)#开启监听模式,此时电脑只要不断电不会息屏 count=0start_time=time.time()  while True:if time.time()-start_time<duration:newMessage,who=Tools.pull_latest_message(chatList)#消息列表内的最后一条消息(listitem)不等于刚打开聊天界面时的最后一条消息(listitem)#并且最后一条消息的发送者是好友时自动回复#这里我们判断的是两条消息(listitem)是否相等,不是文本是否相等,要是文本相等的话,对方一直重复发送#刚打开聊天界面时的最后一条消息的话那就一直不回复了if newMessage!=initial_last_message and who==friend:edit_area.click_input()pyautogui.hotkey('ctrl','v',_pause=False)pyautogui.hotkey('alt','s',_pause=False)count+=1else:breakif count:if save_chat_history:chat_history=get_chat_history(friend=friend,number=2*count,capture_screen=capture_screen,folder_path=folder_path,wechat_path=wechat_path,is_maximize=is_maximize,close_wechat=close_wechat)  return chat_historySystemsettings.close_listening_mode()if close_wechat:main_window.close()

        pywechat内的auto_reply_to_friend函数用来自动回复指定好友指定内容,其主要接受两个参数:friend与content,内部的实现机制是定时轮询查询聊天界面新消息,如果有新消息那么ctrl c v粘贴指定内容alt s发送,这里我们希望将其修改为自动根据好友信息内容进行回复,那么便可以将这个函数作为到参数的修饰器,被修饰的函数用来替代原有的指定的content并按照新消息内容返回要发送的回复内容。也就是说我们最终希望的是这个样子(有点像智能客服了):

转换思路:

 ·     首先与我们前边说到的转换思路一致,我们要确定复用的代码部分

       对于auto_reply_to_friend来说整个函数内部的代码基本上都可以复用

        接着还需要确定修改函数的行为:

        在源代码中,因为回复内容是固定,所以直接在轮询开始前就将要回复的内容content(需要传入的参数)复制到剪贴板,然后复制粘贴发送。 

        如果要实现根据内容自定义,那么我们按照这样的格式修改即可,定义一个reply_content变量,其结果为reply_func这一被修饰函数的返回值,其主要作用就是根据传入的内容返回对应的回复内容

def reply_func(newMessage):if '你好' in newMessage:return '你好,有什么需要帮助的吗?'if '在吗' in newMessage:return '不好意思,当前不在,请稍后联系'return '我的微信正在被pywechat控制' 

 然后将修改后的代码全部放到前边说到的带参数的装饰器的wrapper函数中:

from functools import wraps
def auto_reply_to_friend_decorator(*args,**kwargs):def decorator(reply_func):@wraps(reply_func)def wrapper():#修改后的代码return wrapperreturn decorator

完整代码:

import time
import pyautogui
from functools import wraps
from pywechat.WechatTools import Tools
from pywechat.WinSettings import Systemsettings
from pywechat.WechatTools import match_duration,mouse
from pywechat.Errors import TimeNotCorrectError,NotFriendError
from pywechat.Uielements import Buttons,Main_window,Texts,Edits,SideBar
Buttons=Buttons()
Main_window=Main_window()
Texts=Texts()
Edits=Edits()
SideBar()
language=Tools.language_detector()
def auto_reply_to_friend_decorator(duration:str,friend:str,search_pages:int=5,delay:int=0.2,wechat_path:str=None,is_maximize:bool=True,close_wechat:bool=True):'''该函数为自动回复指定好友的修饰器\nArgs:friend:好友或群聊备注\nduration:自动回复持续时长,格式:'s','min','h',单位:s/秒,min/分,h/小时\nsearch_pages:在会话列表中查询查找好友时滚动列表的次数,默认为5,一次可查询5-12人,当search_pages为0时,直接从顶部搜索栏搜索好友信息打开聊天界面\nfolder_path:存放聊天记录截屏图片的文件夹路径\nwechat_path:微信的WeChat.exe文件地址,主要针对未登录情况而言,一般而言不需要传入该参数,因为pywechat会通过查询环境变量,注册表等一些方法\n尽可能地自动找到微信路径,然后实现无论PC微信是否启动都可以实现自动化操作,除非你的微信路径手动修改过,发生了变动的话可能需要\n传入该参数。最后,还是建议加入到环境变量里吧,这样方便一些。加入环境变量可调用set_wechat_as_environ_path函数\nis_maximize:微信界面是否全屏,默认全屏。\nclose_wechat:任务结束后是否关闭微信,默认关闭\n'''def decorator(reply_func):@wraps(reply_func)def wrapper():if not match_duration(duration):#不按照指定的时间格式输入,需要提前中断退出raise TimeNotCorrectErroredit_area,main_window=Tools.open_dialog_window(friend=friend,wechat_path=wechat_path,is_maximize=is_maximize,search_pages=search_pages)voice_call_button=main_window.child_window(**Buttons.VoiceCallButton)video_call_button=main_window.child_window(**Buttons.VideoCallButton)if not voice_call_button.exists():#公众号没有语音聊天按钮main_window.close()raise NotFriendError(f'非正常好友,无法自动回复!')if not video_call_button.exists() and voice_call_button.exists():main_window.close()raise NotFriendError('auto_reply_to_friend只用来自动回复好友,如需自动回复群聊请使用auto_reply_to_group!')chatList=main_window.child_window(**Main_window.FriendChatList)#聊天界面内存储所有信息的容器initial_last_message=Tools.pull_latest_message(chatList)[0]#刚打开聊天界面时的最后一条消息的listitem   Systemsettings.open_listening_mode(full_volume=False)#开启监听模式,此时电脑只要不断电不会息屏 start_time=time.time()  while True:if time.time()-start_time<match_duration(duration):#将's','min','h'转换为秒newMessage,who=Tools.pull_latest_message(chatList)#消息列表内的最后一条消息(listitem)不等于刚打开聊天界面时的最后一条消息(listitem)#并且最后一条消息的发送者是好友时自动回复#这里我们判断的是两条消息(listitem)是否相等,不是文本是否相等,要是文本相等的话,对方一直重复发送#刚打开聊天界面时的最后一条消息的话那就一直不回复了if newMessage!=initial_last_message and who==friend:reply_content=reply_func(newMessage)Systemsettings.copy_text_to_windowsclipboard(reply_content)pyautogui.hotkey('ctrl','v',_pause=False)time.sleep(delay)pyautogui.hotkey('alt','s',_pause=False)else:breakSystemsettings.close_listening_mode()if close_wechat:main_window.close()return wrapperreturn decorator@auto_reply_to_friend_decorator(duration='60s',friend='测试ing365')
def reply_func(newMessage):if '你好' in newMessage:return '你好,有什么需要帮助的吗?'if '在吗' in newMessage:return '不好意思,当前不在,请稍后联系'return '我的微信正在被pywechat控制'   
reply_func()

运行效果:

总结: 

        以上便是python语法入门之装饰器的基本用法一文的所有内容,如果感觉对你有用,还请一个免费的三连支持一下博主,感谢 

http://www.lryc.cn/news/589648.html

相关文章:

  • 21-C#的委托简单使用-1
  • 移动碰撞法 ——套料排版算法——CAD c#
  • 一文读懂循环神经网络—门控循环单元
  • Agentic AI 的威胁与缓解措施
  • 李白周游记50篇
  • MySQL锁机制与SQL优化详解
  • 学习C++、QT---26(QT中实现记事本项目实现文件路径的提示、C++类模板、记事本的行高亮的操作的讲解)
  • 应用部署作业-02-流程
  • C++-linux系统编程 8.进程(二)exec函数族详解
  • Qt .pro中的.pri详解(四)
  • 【Trea】Trea国际版|海外版下载
  • 【NBA】75 Greatest NBA Players of All Time
  • 【Android】日志的使用
  • 永磁同步电机控制算法--弱磁控制(定交轴CCR-FQV)
  • 内存的基础相关知识,什么是内存,内存管理
  • 【MCU控制 初级手札】1.1 电阻
  • 高等数学强化——导学
  • 清理C盘--办法
  • 腾讯云智一面---后台开发(凉经)
  • 课题学习笔记1——文本问答与信息抽取关键技术研究论文阅读(用于无结构化文本问答的文本生成技术)
  • linux系统------HAProxy 配置
  • 部署本地大模型 Ollama + LLaMA3
  • 19.如何将 Python 字符串转换为 Slug
  • 希尔排序:突破传统排序的边界
  • JAVA进阶--设计模式
  • 华为OD 特异双端队列
  • TDengine GREATEST 和 LEAST 函数用户手册
  • DirectX12(D3D12)基础教程九 间接绘制
  • Unity灯光面板环境设置
  • 区块链发展史全景长图