python装饰器详解之应用

前言

在上一篇文章《python装饰器详解之语法》中,我着重分析了python装饰器的语法,装饰器提高了程序的可扩展性、灵活性及可复用性,若将其用在日常开发中会大大提高生产效率。但是面对装饰器,很多人特别是python新人并不知道装饰器应该使用在什么场景下,也不知道如何使用会使程序更加优雅。针对于此种问题,博主希望通过对实际案例的分析来进一步解说python装饰器,大家也可以从中获取一些灵感。

场景介绍

本文设计了一个无人机检查系统,无人机飞控系统中会有很多的检查,涉及到很多的检查项,为了方便管理我们将这些检查项进行分门别类。分类基于两个维度:检查阶段检查模式

  • 检查阶段分为:起飞前和飞行中
  • 检查模式分为:基础检查和可选检查

这里我们设计了四种检查:

  • 电源检查,检查电量是否足够
  • 通信检查,检查无线通信是否可用
  • 相机检查,检查相机是否可用
  • 位置检查,检查当前经纬度及高度

我们对四种检查进行分类:

起飞前飞行中
基础检查电源检查、通信检查电源检查
可选检查相机检查相机检查、位置检查

从上表中可以发现,同一个检查会出现在不同的阶段和不同模式下。

需求

我们只需要将检查阶段和检查模式作为输入,系统即可自动执行相关的检查。
比如,输入起飞前和基础检查,则执行电源检查通信检查

代码实现

基于if分支的实现

很多人看到这个场景和需求,第一想到的就是通过if语句实现。ok,我们就先用最简单的方式来实现这一功能:

# 示例1
# 检查项代码实现
def check_power(*args,**kw):
    '检查电源'
    return "power OK!"

def check_comm(*args,**kw):
    '检查通信'
    return "com OK!"

def check_camera(*args,**kw):
    '检查相机'
    return "cam OK!"

def check_position(*args,**kw):
    '检查位置'
    return "position OK!"
    
def start_check(chk_time, chk_mode):
	# 检查
	if chk_time=='before_fly' and chk_mode=='base':
		print(check_power())
		print(check_comm())
	elif chk_time=='flying' and chk_mode=='base':
		print(check_power())
	elif chk_time=='before_fly' and chk_mode=='opt':
		print(check_camera())
	elif chk_time=='flying' and chk_mode=='opt':
		print(check_camera())
		print(check_position())
		
if __name__=='__main__':
	start_check('before_fly', 'base')
'''
执行输出:
power OK!
com OK!
'''

示例1就是最朴素的实现方法,而且语法上也很好理解。但这种实现方法有很多问题,其中一个就是 可扩展性不足。我们通过下面两种场景进行简单分析:

  1. 表现在代码组织上,目前我们的检查项只有两个维度上的分类:检查阶段和检查模式,切每个维度只有两个值,此时if语句只有4中分支;当我们后期增加分类维度的时候,或者增加每个维度的取值数量时,if语句就会变量冗长,比如增加到3个维度,每个维度两个值,那if分支就会增加到8个。这种情况代码的可读性就会很差,可维护性也不好,后期修改代码会比较麻烦。
  2. 可扩展性不足的另一个场景,我们考虑在每个检查前都做一个检查前准备和检查后结果打包处理,那我们就要去修改每个分支或者修改每个检查函数,这个时候的要付出的代价也是很高的。

基于装饰器实现

对于以上问题,我们基于装饰器实现同样的功能,看下效果如何:

# 示例2
from functools import wraps

g_dictRunTime={}
'''
g_dictRunTime 形如
{
    "before_fly":[func1,func2]
    "after_fly":[func1]
}
'''

g_dictRunMode={}
'''
g_dictRunMode形如
{
    "manual":['func1','func2'] 
    "auto":['func1']
}
'''

def run_time_registe(*runTimes):
    def decorator(func):
        for runTime in runTimes:
            if runTime not in g_dictRunTime.keys(): 
                g_dictRunTime[runTime] = list()
            g_dictRunTime[runTime].append(func)
        @wraps(func)
        def wrapper(*args,**kw):
            return func(args,kw)
        return wrapper
    return decorator

def run_mode_registe(*runModes):
    
    def decorator(func):
        for runMode in runModes:
            if runMode not in g_dictRunMode.keys():
                g_dictRunMode[runMode] = list()
            g_dictRunMode[runMode].append(func.__name__)
        @wraps(func)
        def wrapper(*args,**kw):
            return func(args,kw)
        return wrapper
    return decorator

@run_time_registe("before_fly", 'flying')
@run_mode_registe("base")
def check_power(*args,**kw):
    '检查电源'
    return "power OK!"


@run_time_registe("before_fly")
@run_mode_registe("base")
def check_comm(*args,**kw):
    '检查通信'
    return "com OK!"

@run_time_registe("before_fly", 'flying')
@run_mode_registe("opt")
def check_camera(*args,**kw):
    '检查相机'
    return "cam OK!"

@run_time_registe("flying")
@run_mode_registe("opt")
def check_position(*args,**kw):
    '检查位置'
    return "position OK!"

def start_check(chk_time, chk_mode):
    for func in g_dictRunTime[chk_time]:
        if func.__name__ in g_dictRunMode[chk_mode]:
            print(func())

if __name__=="__main__":
    start_check('before_fly', 'base')
'''
执行输出:
power OK!
com OK!
'''

示例2使用装饰器来实现了示例1中同样的功能,不熟悉装饰的小伙伴看到这块代码可能会有点蒙,此处不对装饰器代码进行详细剖析,这块理解不好的可以看我上一篇文章python装饰器详解之语法。有遗憾的地方也可以在以上的代码的基础上修改实验,都是可以得到答案的。
这里通过两个装饰器run_time_registe()run_mode_registe()将不同的检查函数注册到了两个dict变量,最后在start_check()中通过for循环调用所需的检查函数。
此时代码的可扩展性就变的很好了,当你想增加新的检查项时,直接定义检查函数,外加装饰器即可。当增加新的分类维度时,可以增加一个装饰器函数。
另外,当我们想在检查前和检查后增加一些处理函数时,也很方便,如下:

# -*- coding:utf-8 -*- 
from functools import wraps
import time

g_dictRunTime={}
'''
g_dictRunTime 形如
{
    "before_fly":[func1,func2]
    "after_fly":[func1]
}
'''

g_dictRunMode={}
'''
g_dictRunMode形如
{
    "base":['func1','func2'] 
    "opt":['func1']
}
'''

def run_time_registe(*runTimes):
    def decorator(func):
        for runTime in runTimes:
            if runTime not in g_dictRunTime.keys(): 
                g_dictRunTime[runTime] = list()
            g_dictRunTime[runTime].append(func)
        @wraps(func)
        def wrapper(*args,**kw):
            return func(args,kw)
        return wrapper
    return decorator

def run_mode_registe(*runModes):
    
    def decorator(func):
        for runMode in runModes:
            if runMode not in g_dictRunTime.keys():
                g_dictRunTime[runMode] = list()
            g_dictRunTime[runMode].append(func.__name__)
        @wraps(func)
        def wrapper(*args,**kw):
            return func(args,kw)
        return wrapper
    return decorator

def prepare_check(func):
    @wraps(func)
    def wrapper(*args,**kw):
        msg = "name: "+func.__name__ +"\nstartTime: "+ time.asctime( time.localtime(time.time()) )
        checkResult = func(args,kw)
        return msg+checkResult
    return wrapper

def pack_msg(func):
    @wraps(func)
    def wrapper1(*args,**kw):
        checkResult = func(args,kw)
        return "\nresult: "+checkResult+'\n'
    return wrapper1


@run_time_registe("before_fly", 'flying')
@run_mode_registe("base")
@prepare_check
@pack_msg
def check_power(*args,**kw):
    '检查电源'
    return "power OK!"


@run_time_registe("before_fly")
@run_mode_registe("base")
@prepare_check
@pack_msg
def check_comm(*args,**kw):
    '检查通信'
    return "com OK!"

@run_time_registe("before_fly", 'flying')
@run_mode_registe("opt")
@prepare_check
@pack_msg
def check_camera(*args,**kw):
    '检查相机'
    return "cam OK!"

@run_time_registe("flying")
@run_mode_registe("opt")
@prepare_check
@pack_msg
def check_position(*args,**kw):
    '检查位置'
    return "position OK!"

def start_check(chk_time, chk_mode):
    for func in g_dictRunTime[chk_time]:
        if func.__name__ in g_dictRunTime[chk_mode]:
            print(func())

if __name__=="__main__":
    start_check( 'before_fly','base')

'''
执行结果:
name: check_power
startTime: Sun Jul 26 12:15:16 2020
result: power OK!

name: check_comm
startTime: Sun Jul 26 12:15:16 2020
result: com OK!
'''

我们增加了两个函数:

  • 检查前准备 prepare_check() ,在检查前提供检查项名称和检查时间
  • 检查后消息打包 pack_msg() ,将检查结果进行简单的包装

通过装饰器,我们可以在不修改原检查函数以及检查调用 start_check() 的情况下,来实现功能的拓展。

结束语

本文通过一个实际的案例,对装饰器的用法以及其作用进行了讲述。对于新手来说,本文代码可能还需要细细品读才能明白每一处的作用,最好能够实际动手操练一下。
大家有何想法可以评论区说一说。

码字不易,如果喜欢可以点赞 关注,博主会将更多有意思的东西分享出来。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值