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

发布于:2025-07-16 ⋅ 阅读:(19) ⋅ 点赞:(0)

        本文,我将为大家详细讲解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+1
        return inner_var
    inner_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 wrapper

def 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 wrapper
    return 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 wrapper
    return inner_decorator

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

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

基本示例

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

#flask风格的路由注册
routes={}
def route(path):  #装饰器工厂
    def decorator(func):  #真正的装饰器
        routes[path]=func  #注册函数到路由表
        return func
    return 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的自动回复某个好友的消息\n
        Args:
            friend:好友或群聊备注\n
            duration:自动回复持续时长,格式:'s','min','h',单位:s/秒,min/分,h/小时\n
            content:指定的回复内容,比如:自动回复[微信机器人]:您好,我当前不在,请您稍后再试。\n
            save_chat_history:是否保存自动回复时留下的聊天记录,若值为True该函数返回值为聊天记录json,否则该函数无返回值。\n
            capture_screen:是否保存聊天记录截图,默认值为False不保存。\n
            search_pages:在会话列表中查询查找好友时滚动列表的次数,默认为5,一次可查询5-12人,当search_pages为0时,直接从顶部搜索栏搜索好友信息打开聊天界面\n
            folder_path:存放聊天记录截屏图片的文件夹路径\n
            wechat_path:微信的WeChat.exe文件地址,主要针对未登录情况而言,一般而言不需要传入该参数,因为pywechat会通过查询环境变量,注册表等一些方法\n
                尽可能地自动找到微信路径,然后实现无论PC微信是否启动都可以实现自动化操作,除非你的微信路径手动修改过,发生了变动的话可能需要\n
                传入该参数。最后,还是建议加入到环境变量里吧,这样方便一些。加入环境变量可调用set_wechat_as_environ_path函数\n
            is_maximize:微信界面是否全屏,默认全屏。\n
            close_wechat:任务结束后是否关闭微信,默认关闭\n
        Returns:
            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=0
        start_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+=1
            else:
                break
        if 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_history
        Systemsettings.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 wrapper
    return 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):
    '''
    该函数为自动回复指定好友的修饰器\n
    Args:
        friend:好友或群聊备注\n
        duration:自动回复持续时长,格式:'s','min','h',单位:s/秒,min/分,h/小时\n
        search_pages:在会话列表中查询查找好友时滚动列表的次数,默认为5,一次可查询5-12人,当search_pages为0时,直接从顶部搜索栏搜索好友信息打开聊天界面\n
        folder_path:存放聊天记录截屏图片的文件夹路径\n
        wechat_path:微信的WeChat.exe文件地址,主要针对未登录情况而言,一般而言不需要传入该参数,因为pywechat会通过查询环境变量,注册表等一些方法\n
            尽可能地自动找到微信路径,然后实现无论PC微信是否启动都可以实现自动化操作,除非你的微信路径手动修改过,发生了变动的话可能需要\n
            传入该参数。最后,还是建议加入到环境变量里吧,这样方便一些。加入环境变量可调用set_wechat_as_environ_path函数\n
        is_maximize:微信界面是否全屏,默认全屏。\n
        close_wechat:任务结束后是否关闭微信,默认关闭\n
    '''
    def decorator(reply_func):
        @wraps(reply_func)
        def wrapper():
            if not match_duration(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.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:
                    break
            Systemsettings.close_listening_mode()
            if close_wechat:
                main_window.close()
        return wrapper
    return 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语法入门之装饰器的基本用法一文的所有内容,如果感觉对你有用,还请一个免费的三连支持一下博主,感谢 


网站公告

今日签到

点亮在社区的每一天
去签到