概述
枚举类作为在项目规模扩大时必须使用的类,其重要性不言而喻。但是有很多时候,由于它的麻烦写法,让很多人容易顺手写个常量或者字符串来代替其语义。我们说编程也是需要语义化编程,也就是当我们编程完毕后,代码本身就像注释一般,可以也很容易的让我们理解代码的含义。
因此,这里,我们从产品经理的角度,分析为什么要使用枚举类的3大需求,使用方法以及2个扩展特性,并在最后进行一个深入的源码拓展。
学习任何一个东西,尤其是语言,不仅仅弄清是什么,更要弄清为什么,尽管我们可能花费更多的时间思考,但是这样记的更牢,理解的更透彻。学习英语是如此,学习编程,也是如此。
1. 不使用枚举类的阶段
枚举类在初学者看来是一个相当鸡肋的类。首先,在我们进行选择、比较的时候,我们更喜欢直接赋值变量,以int或者string类型的最多,比如当我们定义一个学生类的性别的时候,我们更喜欢使用male
或者female
来进行赋值。
class student:
def __init__(self):
self.gender="male" # or female
...
或者当我们进行判断时,我们又长又乱的if-else
语句其判断条件都是手写的赋值,这样一方面难以理解其含义,另一方面,你很难控制你的data_num
赋值时能够赋予他合法的值,而且执行无误,因为我们总会有一个else
兜底。
if data_num=1:
print()
elif data_num=2:
write()
elif data_num=3:
exec()
...
else:
unexception()
因此,我们可能有以下3个需求需要用到枚举:
- 当我们需要一个固定的变量时,我们能够很好的记住所有的选项。
- 当我们赋予一个可能具有多个离散值的变量时,我们希望它是合法并且符合预期的。
- 当我们阅读到一个具有多个离散值的变量时,我们需要能够非常容易的理解其含义。
2. 有枚举思想的阶段
有了这些需求,程序员就开始动脑筋了,我们该如何能够实现刚才的需求呢?作为天才的你一定发现了,我们可以使用python中的字典或者类来实现刚才的功能。
2.1 使用字典实现
比如,当我们需要一个颜色枚举的时候,我们就使用字典实现,如下代码:
# 使用字典
Color = {
'RED': 1,
'GREEN': 2,
'BLUE': 3,
}
# 赋值一个图片的元素的例子
class Picture:
def __init__(self):
self.sun_color = Color["RED"]
self.sky_color = Color["BLUE"]
self.grass_color = Color["GREEN"]
def update_sun_color(self, color):
self.sun_color = color
if __name__ == '__main__':
custom_picture = Picture()
new_color = Color["GREEN"]
custom_picture.update_sun_color(new_color)
print(custom_picture.sun_color) # 2
那么这样的改变,满足了刚才的几个需求呢?回过头看,我们看到主要满足了第2和第3条需求,第一条需求仍然没有被满足。因为我们需要一个能够有可选项的编码,而不是自己填。因此使用类也可以解决这个问题。
2.2 使用类实现
class Color:
RED = 1
GREEN = 2
BLUE = 3
# 赋值一个图片的元素的例子
class Picture:
def __init__(self):
self.sun_color = Color.RED
self.sky_color = Color.BLUE
self.grass_color = Color.GREEN
def update_sun_color(self, color):
self.sun_color = color
if __name__ == '__main__':
custom_picture = Picture()
new_color = Color.GREEN
custom_picture.update_sun_color(new_color)
print(custom_picture.sun_color) # 2
这样看似问题都解决了,也能够可读,也能够候选。
但是其实它还有两个缺陷,第一个是,它的第二条会在比较时发生问题,即如果出现了预期之外的值的时候,没有办法处理,比如我们这里就只有3个颜色,如果我们新的值是5,我们就很难处理。常见的处理方法比如加一个default用来表示其他值,但是有可能是无穷尽个不符合预期的值呢?第二个,这个属性的值是会被改变的,下面一个例子就揭示了一个非常吓人的场景。
if __name__ == '__main__':
custom_picture = Picture()
Color.GREEN = 3 # 新加的一句话
new_color = Color.GREEN
custom_picture.update_sun_color(new_color)
print(custom_picture.sun_color) # 3
print(custom_picture.sun_color == custom_picture.sky_color) # 非常诡异的True
上面的例子可以看出,我们只需要增加一句话,太阳和天空的颜色居然是一样的,尽管我们赋值时,以为自己赋予的是绿色!因此,枚举类应运而生了。它将满足上面的三个需求,我们会一一讲解。
3. 使用枚举的阶段
综合上面的例子,我们可以看到,枚举真的非常有必要使用,曾经不使用枚举的我,陷入了很多代码的巨坑!回归正题,我们首先看一个枚举类的例子。
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
我们只需要继承一个枚举类,就可以完全的避免刚才普通类出现的问题,当你再次非法赋值的时候,你会收到如下的警告:
raise AttributeError('Cannot reassign members.')
AttributeError: Cannot reassign members.
因此就可以避免非法的重新赋值的场景,能够满足1,2,3条需求。当然你也可以用以下语句更加便捷的创建一个枚举类:
Color = Enum("Color",('RED','GREEN ','BLUE '))
3.1 枚举类的使用与特性
刚才我们主要见识了枚举类和一般类的区别和样子,我们现在看看如何正确使用枚举类,还是以刚才的例子:
from enum import Enum
# 创建一个枚举类
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
blue = 2 # 别名
#调用枚举成员的 3 种方式
print(Color.RED) # Color.RED
print(Color['RED']) # Color.RED
print(Color(1)) # Color.GREEN
#调取枚举成员中的 value 和 name
print(Color.RED.value) # 0
print(Color.RED.name) # RED
#遍历枚举类中所有成员的 2 种方式
for color in Color:
print(color) # Color.RED Color.GREEN Color.BLUE
for color in Color.__members__:
print(color) # RED GREEN BLUE blue
从上面我们可以看到,枚举类甚至可以做出不同名,同值的情况,第二个名称会被认定为别名,在普通遍历时,是不会被看到的。
当我们幻想着有其他非法值时print(Color(3))
,它会和字典一样自动弹出异常:
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 3 is not a valid Color
3.2 枚举类的扩展特性
通过刚才的讲解,我们知道枚举类除了满足我们的3大需求外,展现出两个特性:不唯一性和不可比较性。如果想要打破这两个特性,我们就不得不使用一些手段了。
1. 唯一性
如果我们想要让枚举变得唯一,只需要增加一个独一性的装饰器即可:
# 创建一个唯一性的枚举类
from enum import Enum, unique
@unique
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
blue = 2
此时就会报错:
(enumeration, alias_details))
ValueError: duplicate values found in <enum 'Color'>: blue -> BLUE
2. 可比较性
枚举的成员可以通过 is 同一性比较或通过 == 等值比较,但是不能够比较大小,其原因就是我们这个枚举类非常的基础,只是从Object类继承,而Object类确实就只能进行等于比较,不能进行大小比较,不过好在我们又更加细致的类IntEnum
,当你需要进行数值比较的时候,可以实现这个类:
from enum import IntEnum
class Shape(IntEnum):
circle = 1
square = 2
class Request(IntEnum):
post = 1
get = 2
print(Shape.circle == 1) # True
print(Shape.circle < 3) # True
print(Shape.circle == Request.post) # True
print(Shape.circle >= Request.post) # True
深入了解枚举阶段
看到枚举类这么神奇,我们就不想看一看枚举到底是如何实现的么?下面给出一个自我实现的枚举和官方的部分源码,由于这里是更加深层次的理解,详情请看《python中enum模块源码的详细分析》。
自我实现一个枚举
就像我们改进字典的缺陷一样,我们可以基于字典构建一个枚举,这里还可以使用__prepare__
魔术方法返回一个类字典实例,但是这个枚举仍然没有解决可以重新赋值的问题。
class _Dict(dict):
def __setitem__(self, key, value):
if key in self:
raise TypeError('Attempted to reuse key: %r' % key)
super().__setitem__(key, value)
class MyMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
d = _Dict()
return d
class Enum(metaclass=MyMeta):
pass
class Color(Enum):
red = 1
red = 1 # TypeError: Attempted to reuse key: 'red'
官方源码解析
官方的代码就强大和复杂的多,下面是部分类及其实现,我们仍然能够看到刚才的想法的影子。另一个我们可学习的是,当我们一个类特别的复杂时,我们需要将其分解为一个有一个小类,注意的是,类的方法只能操纵类原有的属性,如果要在类的更上一层进行操作和管理时,可以再创建一个更高级的管理者来管理我们所需要的类;而当你需要将类进入到不同的场景以细化时,可以使用继承出子类进行操作,省去很多重复代码。
class _EnumDict(dict):
def __init__(self):
super().__init__()
self._member_names = []
...
def __setitem__(self, key, value):
...
elif key in self._member_names:
# descriptor overwriting an enum?
raise TypeError('Attempted to reuse key: %r' % key)
...
self._member_names.append(key)
super().__setitem__(key, value)
class EnumMeta(type):
@classmethod
def __prepare__(metacls, cls, bases):
enum_dict = _EnumDict()
...
return enum_dict
class Enum(metaclass=EnumMeta):
...
最后
以上就是坦率棉花糖为你收集整理的我们为什么要用枚举类?从产品经理的角度,手把手带你走进enum的神奇世界1. 不使用枚举类的阶段2. 有枚举思想的阶段3. 使用枚举的阶段深入了解枚举阶段的全部内容,希望文章能够帮你解决我们为什么要用枚举类?从产品经理的角度,手把手带你走进enum的神奇世界1. 不使用枚举类的阶段2. 有枚举思想的阶段3. 使用枚举的阶段深入了解枚举阶段所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复