一日一技:使用装饰器简化大量if判断(二)

简介: 一日一技:使用装饰器简化大量if判断(二)

今天我们就来看看大于小于应该怎么来判断。为了实现我们今天的目标,有两个前置知识需要掌握,一个是Python自带的operator模块,另一个是偏函数。


2 > 1还有另一种写法?


当我们要表达大于这个意思的时候,你想到的肯定是大于符号>。所以2大于1,肯定写作2 > 1。这看起来是很正常的事情。现在,如果我让你不准使用大于符号>,怎么表示大于?


实际上,在Python里面,除了>外,还有一种写法,就是使用自带的operator模块:


import operator
operator.gt(2, 1)


其中的.gt(参数1, 参数2)就表示参数1 > 参数2。如果成立,返回True,否则返回False


类似的还有:


  • 大于等于:operator.ge


  • 小于:operator.lt


  • 小于等于:operator.le


  • 不等于:operator.ne


  • 等于:operator.eq


因此,下面两个写法是等价的:


if a <= b:
    print('成功')


if operator.le(a, b):
    print('成功')


偏函数


我在很久以前的公众号文章里面已经介绍过偏函数了:偏函数:在Python中设定默认参数的另一种办法。因此本文就不再讲它的基础用法了,大家点击链接去看那篇文章就可以掌握。


为什么我们需要偏函数呢?这是因为我们今天要做的事情,它需要给函数先传一半的参数,另一半的参数要在未来才能传入。例如,循环等待用户输入数字,如果其中一次输入的数字大于等于5,就打印你好世界


如果不知道偏函数,你可能是这样写的:


while True:
    num = int(input('请输入数字:'))
    if num >= 5:
        print('你好世界')


有了偏函数以后,你的写法是这样的:


import operator
from functools import partial
ge_5 = partial(operator.le, 5)
while True:
    num = int(input('请输入数字:'))
    if ge_5(num):
        print('你好世界')


特别注意,这里我在偏函数中传入的第一个参数是operator.le:小于。因为operator.xx表示第一个参数对第二个参数的比较,所以x >= 5 就相当于5 <= x 也就是operator.le(5, x)


在装饰器中实现大小比较


前置知识掌握以后,我们就能看如何在装饰器里面实现大小比较。在第一篇文章中,我们只实现了参数等于,它的原理是:


def register(value):
        def wrap(func):
            if value in registry:
                raise ValueError(
                    f'@value_dispatch: there is already a handler '
                    f'registered for {value!r}'
                )
            registry[value] = func
            return func
        return wrap


register只接收了一个位置参数value。但实际上,我们还可以通过修改这段注册的代码,实现如下的效果:


@get_discount.register(3, op='gt')
def parse_level_gt3(level):
    print('等级大于3')
@get_discount.register(3, op='le')
def parse_level_le3(level):
    print('等级小于等于3')


有同学问,有没有可能实现这样的写法呢:


@get_discount.register(2, le=3)
def parse_level_gt3(level):
    print('等级为2')


我觉得这样写是没有什么必要的。因为register()里面,多个参数之间的关系是。那么只有两种情况,要么,就等于这个数,例如@get_discount.register(2,

le=3),既要等于2,又要小于等于3,那显然就等于2。不需要写这个le=3。要么,就不存在结果,例如@get_discount.register(2, gt=3),既要等于2,又要大于3,显然下面被装饰的函数永远不会执行。因为找不到这个数。


因此,我们的装饰器函数就可以做如下修改:


import functools
import operator
def value_dispatch(func):
    registry_eq = {}
    registry_other = {}
    key_op_map = {}
    @functools.wraps(func)
    def wrapper(arg0, *args, **kwargs):
        if arg0 in registry_eq:
            delegate = registry_eq[arg0]
            return delegate(arg0, *args, **kwargs)
        else:
            for key, op in key_op_map.items():
                if op(arg0):
                    delegate = registry_other[key]
                    return delegate(arg0, *args, **kwargs)
        return func(arg0, *args, **kwargs)
    def register(value, op='eq'):
        if op == 'eq':
            def wrap(func):
                if value in registry_eq:
                    raise ValueError(
                        f'@value_dispatch: there is already a handler '
                        f'registered for {value!r}'
                    )
                registry_eq[value] = func
                return func
            return wrap
        else:
            if op == 'gt':
                op_func = functools.partial(operator.lt, value)
            elif op == 'ge':
                op_func = functools.partial(operator.le, value)
            elif op == 'lt':
                op_func = functools.partial(operator.gt, value)
            elif op == 'le':
                op_func = functools.partial(operator.ge, value)
            else:
                raise ValueError('op 参数只能是:gt/ge/lt/le之一')
            key = f'{op}_{value}'
            key_op_map[key] = op_func
            def wrap(func):
                if key in registry_other:
                    raise ValueError(
                        f'@value_dispatch: there is already a handler '
                        f'registered for {key!r}'
                    )
                registry_other[key] = func
                return func
            return wrap
    wrapper.register = register
    return wrapper


它的使用方法还是跟以前一样,先定义默认的函数逻辑:


@value_dispatch
def get_discount(level):
    return '等级错误'



如果定义相等的逻辑,写法跟以前完全一样:


@get_discount.register(1)
def parse_level_1(level):
    "大量计算代码"
    discount = 0.1
    return discount


如果要定义不等于逻辑,就在.register()中添加一个参数op


@get_discount.register(2, op='gt')
def parse_level_gt2(level):
    discount = 1
    return 1


运行效果如下图所示:


a.png


由于我们定义了大于2时,始终返回1,所以可以看到get_discount(6)get_discount(10)返回的都是1.


由于我们只定义了等于1和大于2的逻辑,所以当传入的参数为2时,就返回等级错误.

到这里,本文要讲的内容就结束了。但最后还是要考大家3个问题:


如果不使用偏函数和operator模块,你会怎么做


你可以试一试在不实用偏函数和operator的情况下,实现这个需求。


如果定义的条件有重叠怎么办?


例如对于下面的两个函数:


@get_discount.register(2, op='gt')
def parse_level_gt2(level):
    discount = 1
    return discount
@get_discount.register(10, op='gt')
def parse_level_gt2(level):
    discount = 100
    return discount


当level的值是20的时候,同时满足两个条件,应该运行哪一个呢?


如何定义区间?


怎么实现这样的功能:


@get_discount.register(ge=2, lt=5)
def parse_level_between2_5(level):
    print('等级2<=level<5')
    discount = 0.5
    return discount


如果区间存在全包含、部分包含应该运行哪个函数?例如:


@get_discount.register(ge=2, lt=00)
...
@get_discount.register(ge=20, lt=50)
...
@get_discount.register(ge=80, lt=200)
...


请大家把你对这两个问题的答案回答在评论区里面。提示(想清楚什么是真需求,什么

是伪需求,再考虑怎么解决)


请关注微信公众号【未闻Code】获取更多精彩文章。

目录
相关文章
|
消息中间件 Java 测试技术
深聊性能测试,从入门到放弃之:Locust性能自动化(一)初识Locust
深聊性能测试,从入门到放弃之:Locust性能自动化(一)初识Locust
639 1
|
11月前
|
数据采集 存储 数据处理
数据治理:如何制定数据标准与规范
在当今这个数据驱动的时代,数据已成为企业最宝贵的资产之一。然而,随着数据量的爆炸性增长和数据来源的多样化,如何有效地管理和利用这些数据成为了企业面临的重大挑战。数据治理作为确保数据质量、安全性、合规性和可访问性的关键过程,其核心在于制定并执行一套科学、合理的数据标准与规范。本文将探讨如何制定数据标准与规范,以推动企业的数据治理实践。
1903 3
|
机器学习/深度学习 算法 调度
深度学习|改进两阶段鲁棒优化算法i-ccg
深度学习|改进两阶段鲁棒优化算法i-ccg
|
存储 算法 安全
Linux三种加密模式
Linux三种加密模式
217 0
|
存储 C语言
C语言中static关键字的作用与用法解析
C语言中static关键字的作用与用法解析
|
JavaScript 开发工具 git
Three.js第1篇,Three.js新手教学,如何在项目中使用Three.js(three.js使用流程详细,three.js的使用方式,three.js创建3d物体)
Three.js封装了WebGL的底层细节,是一款运行在浏览器中的 3D 引擎,可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,目前在Git上已经拥有90k+的star。
545 0
Three.js第1篇,Three.js新手教学,如何在项目中使用Three.js(three.js使用流程详细,three.js的使用方式,three.js创建3d物体)
|
消息中间件 弹性计算 固态存储
256变4096:分库分表扩容如何实现平滑数据迁移?
本文作者就一个高德打车弹外订单系统进行了一次扩分库分表和数据库迁移。
256变4096:分库分表扩容如何实现平滑数据迁移?
|
安全 Serverless 数据安全/隐私保护
阿里云主导《Serverless 计算安全指南》国际标准正式立项!
阿里云主导《Serverless 计算安全指南》国际标准正式立项!
|
机器学习/深度学习 存储 并行计算
PyTorch的主要组成模块和实战
PyTorch的主要组成模块和实战
195 0
PyTorch的主要组成模块和实战
域名备案后,有必要做个网站吗?为了不掉备案,很有必要!
今天突然一个同学找我咨询阿里云网站备案问题。说是收到了这样一封来自阿里云的邮件: 原因很简单,他当时有台短期服务器,还有个域名,就做了网站备案。虽然一直没有实际制作网站发布,但是网站毕竟做完了备案工作。
5342 0