您当前的位置:首页 >> 综合 >> 
枚举类型让 Python 代码更加优雅易读|每日短讯

时间:2023-06-02 14:03:33    来源:个人图书馆-汉无为

背景

现在的 Python 项目越来越大,一个模型可能就有十几万行。以前没有枚举的时候我们是常量满天飞,Python-3.4 给我们带来了对枚举类型的支持,新的编码方式不管是在可读性、安全性都有不错的提升。


(资料图)

要讲清楚枚举类型的好处,还要从没有枚举类型的时候社区的编码风格说起,下面我们一步步体验枚举类型。

以前的写法

大多数情况下程序员定义枚举其实就是为了定义常量,这也就是我以前觉得 Python 没有枚举类型也没事;既然它已经有了我们还是看一下它的优点和不足。

假设我们要定义一些颜色与日期相关的常量、还有一个用来检查给定日期是不是工作日的小函数,以前我们的代码可能像是这样。

#/usr/bin/env python3# -*- coding: utf8 -*-# RGB 常量RED = 1GREEN = 2BLUE = 3# 周一到周天常量MONDAY = 1TUESDAY = 2WEDNESDAY = 3THURSDAY = 4FRIDAY = 5SATURDAY = 6SUNDAY = 7def is_work_day(weekday):"""检查weekday是不是工作日Paramter:---------weekday: int星期几Return:-------boolExample:is_weekend(1) -> Trueis_weekend(6) -> Falseis_weekend(7) -> False"""# 检查是不是工作日return weekday in (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY)def main():#注意传递的是RED 这里会打印True,明显不应该的print(is_work_day(RED))if __name__ == "__main__":main()

运行结果

python3 main.py True

运行结果为 True ,也就是说按现在的这套逻辑“红色”和“星期一”是一样的。这个就是常量最为致命的一个点“非类型安全”,另一个问题就是他们的打印输出都是整数,这个就使得输出的可读性不强,最后一个就写法上也不优雅(在 Python 中不优雅就是有罪)。

枚举类型输出更加友好

使用枚举最为直观的收益就是它的打印更加友好(内部通过重载 __repl__ 自动实现),下面看例子。

#/usr/bin/env python3# -*- coding: utf8 -*-from enum import Enum# RGB 常量RED = 1GREEN = 2BLUE = 3class Colors(Enum):RED = 1GREEN = 2BLUE = 3print("const value {}\nenum value {}".format(RED, Colors.RED))

从输出结果我们可以看到确实是更加的友好了。

python3 main.py#一个输出是整数,另一个看起来就是见名知意const value 1enum value Colors.RED

枚举类型能做到类型安全

这里是多数常量相关Bug 的源头,在使用常量的年代,我们把“红色”和“周一”都设置成了 1 ,当我们在比较操作时这两个常量会相等,但是业务逻辑上我们并不希望他们相等。

#/usr/bin/env python3# -*- coding: utf8 -*-from enum import Enum# RGB 常量RED = 1...# 周一到周天常量MONDAY = 1...# 这样比较的话它一定是 True , 可能我们在业务逻辑上并不希望它这样print(RED == MONDAY)

使用枚举的话这种 Bug 就不会发生了,下面看使用枚举时的代码。

#/usr/bin/env python3# -*- coding: utf8 -*-from enum import Enum# RGB 常量RED = 1GREEN = 2BLUE = 3class Colors(Enum):RED = 1GREEN = 2BLUE = 3class Weekdays(Enum):MONDAY = 1TUESDAY = 2WEDNESDAY = 3THURSDAY = 4FRIDAY = 5SATURDAY = 6SUNDAY = 7# 下面两个比较都会返回 False ,原因是类型不同就直接返回 False 都不用比较值print("RED == Colors.RED => {}".format(RED == Colors.RED))print("Colors.RED == Weekdays.MONDAY => {}".format(Colors.RED == Weekdays.MONDAY))

运行结果

python3 main.pyRED == Colors.RED => FalseColors.RED == Weekdays.MONDAY => False

枚举类型让代码更加优雅

Python 中许多看起来优雅的语法多数是运算符重载的功劳。枚举类型也在这方面做足了工作,下面看例子。

使用常量时,我们检查给定的日期是不是工作日,代码如下。

defis_work_day(weekday):# 检查是不是工作日return weekday in (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY)

看这段代码的时候我还要用脑子想一下它是通过什么方式在“工作日”和“常量”之间建立联系的。如果我们用枚举类型可以说是所见即所得,总的来讲对人类更加友好。

from enum import Flagclass Weekdays(Flag):MONDAY = 1TUESDAY = 2WEDNESDAY = 4THURSDAY = 8FRIDAY = 16SATURDAY = 32SUNDAY = 64def is_work_day_enum(weekday):#这里会使用位运算来进行比较return weekday == Weekdays.MONDAY | Weekdays.TUESDAY | Weekdays.WEDNESDAY | Weekdays.THURSDAY | Weekdays.FRIDAY

枚举类型性能会差上不少

前面我们说过许多枚举类型的好处,宏观上集中在两点 1、更加安全以减小我们写出 Bug 的可能性,2、语法上更加优雅。

它的缺点就是性能会差不少,先看测试结果吧,测试代码后面会给到。

执行 100w 次比较,看常量与枚举的耗时情况,详细的代码如下。

#/usr/bin/env python3# -*- coding: utf8 -*-"""枚举类型的常见使用方式与性能测试作者:蒋乐兴(初代庄主)时间:2023-01"""from enum import Enum, Flagfrom datetime import datetime# RGB 常量RED = 1GREEN = 2BLUE = 3# 周一到周天常量MONDAY = 1TUESDAY = 2WEDNESDAY = 3THURSDAY = 4FRIDAY = 5SATURDAY = 6SUNDAY = 7class Colors(Enum):RED = 1GREEN = 2BLUE = 3# 使用 Flag 以它支持位操作,这样会更快一些(相比 Enum)class Weekdays(Flag):MONDAY = 1TUESDAY = 2WEDNESDAY = 4THURSDAY = 8FRIDAY = 16SATURDAY = 32SUNDAY = 64def is_work_day(weekday):"""检查 weekday 是不是周末Paramter:---------weekday: int周几Return:-------boolExample:is_weekend(1) -> Trueis_weekend(6) -> Falseis_weekend(7) -> False"""# 检查是不是工作日return weekday in (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY)def is_work_day_enum(weekday):# 第一种写法#return weekday in (Weekdays.MONDAY, Weekdays.TUESDAY, Weekdays.WEDNESDAY, Weekdays.THURSDAY, Weekdays.FRIDAY)# 第二种写法,会更加优雅,但是性能会更加差# 这里会使用位操作来进行比较return weekday == Weekdays.MONDAY | Weekdays.TUESDAY | Weekdays.WEDNESDAY | Weekdays.THURSDAY | Weekdays.FRIDAYdef main():"""比较在特定算法下常量和枚举的性能差异"""choices = [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]start = datetime.now()for i in range(1000000):index = i % 7is_work_day(choices[index])end = datetime.now()print("使用常量耗时 {}".format(end - start))choices = [Weekdays.MONDAY, Weekdays.TUESDAY, Weekdays.WEDNESDAY, Weekdays.THURSDAY, Weekdays.FRIDAY, Weekdays.SATURDAY, Weekdays.SUNDAY]start = datetime.now()for i in range(1000000):index = i % 7is_work_day_enum(choices[index])end = datetime.now()print("使用枚举类型耗时 {}".format(end - start))if __name__ == "__main__":main()

运行结果如下。

python3 main.py使用常量耗时 0:00:00.170633使用枚举类型耗时 0:00:02.534917

100w 次比较用时 2s 多,多数程序代码应该也不会有这么高的要求。我这边建议还是尽可能的使用枚举,毕竟不写Bug 才是第一位的,至于性能问题不用过早的优化(除非是特别明显的性能问题)。

最后

都到这里了,是时候图穷匕见了!我这人比较 real 就直说了,我想涨粉帮忙点下关注!我的技术文章质量还可以,关注应该不亏。

另外 “在看” +“分享” +“点赞”+“收藏”也是我继续写下去的动力;再次感谢!!!

标签: