enum模块(枚举类型)

enum模块: python enum模块提供了枚举功能。有些编程语言是内置了枚举类型,但python没有内置,只是用enum模块里的Enum类来实现类似功能。
如果不使用枚举,我们一般会采用定义常量的方式来处理。比如下面代码:
RED, GREEN, YELLOW = range(3)

下面演示了一个常见的enum类的创建:

from enum import Enum

class Day(Enum):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7

print(list(Day))

# [<Day.MONDAY: 1>, <Day.TUESDAY: 2>, <Day.WEDNESDAY: 3>, <Day.THURSDAY: 4>, <Day.FRIDAY: 5>, <Day.SATURDAY: 6>, <Day.SUNDAY: 7>]

print(type(Day))  # <class 'enum.EnumType'>
print(type(Day.MONDAY))  # <enum 'Day'>
print(Day.MONDAY)  # Day.MONDAY

枚举类型是单例模式,类中的每个成员都是该枚举类的一个实例。枚举类一旦创建,其成员将不可变。包括不能增加或减少,也不能重新赋值。
Day.MONDAY = 0 这样的代码会报错。

还可以像下面这样简单的对其成员赋值,一旦类创建好后,这些类成员就是这个类的实例。

from enum import Enum

class Season(Enum):
    WINTER, SPRING, SUMMER, FALL = range(1, 5)

Enum也是类,所以其不光有其枚举型的成员,还可以自定义方法。一个enum可以继承另一个没有成员的enum。 所以,定义没有成员的enum主要是为了定义一些方法。比如下面示例:

from enum import Enum
import string

class BaseTextEnum(Enum):
    def as_list(self):
        try:
            return list(self.value)
        except TypeError:
            return [str(self.value)]


class Alphabet(BaseTextEnum):
    LOWERCASE = string.ascii_lowercase
    UPPERCASE = string.ascii_uppercase


Alphabet.LOWERCASE.as_list()  # ['a', 'b', 'c', 'd', ..., 'x', 'y', 'z']

还可以使用函数来创建enum,但这一般用于动态编程。 该函数签名如下:
Enum(
value,
names,
*,
module=None,
qualname=None,
type=None,
start=1
)
一般我们只用前两个参数。 第一个是一个字符串,用来代表枚举类型的名称。 第二个names参数可以是一个列表,一个字典等,或一个由二元组构成的列表。用于代表每个成员,即枚举值。使用字典可以同时指定name和value。使用列表则只指定name,value会被自动生成。

from enum import Enum

HTTPMethod = Enum(
    "HTTPMethod", ["GET", "POST", "PUSH", "PATCH", "DELETE"]
)

上面使用Enum函数创建的enum枚举类等价于下面这种常规方法:

from enum import Enum

class HTTPMethod(Enum):
    GET = 1
    POST = 2
    PUSH = 3
    PATCH = 4
    DELETE = 5

auto方法: 用于自动生成枚举成员的value。有时我们并不在乎成员的value。这时可以使用auto方法来自动生成。 同时我们还可以通过定义_generate_next_value_方法来定制化value的生成逻辑。

from enum import Enum, auto

class CardinalDirection(Enum):
    def _generate_next_value_(name, start, count, last_values):
        return name[0]
    NORTH = auto()
    SOUTH = auto()
    EAST = auto()
    WEST = auto()

print(list(CardinalDirection))

输出结果:

[    <CardinalDirection.NORTH: 'N'>,    <CardinalDirection.SOUTH: 'S'>,    <CardinalDirection.EAST: 'E'>,    <CardinalDirection.WEST: 'W'>]

aliases(别名): 枚举成员的name是不能重复的,但不同的成员却可以有相同的value。 但这种情况下,后出现的成员将被认定为前面成员的别名。 但我们可以使用__members__方法来访问所有成员,包括别名。 通过id()函数验证成员,发现成员及其别名返回的id是相同的,说明它们就是指向同一个对象。

from enum import Enum

class OperatingSystem(Enum):
    UBUNTU = "linux"
    MACOS = "darwin"
    WINDOWS = "win"
    DEBIAN = "linux"

print(id(OperatingSystem.UBUNTU))  # 3088959311888
print(id(OperatingSystem.DEBIAN))  # 3088959311888
print(id(OperatingSystem.MACOS))   # 3088959311952

# Aliases aren't listed
print(list(OperatingSystem))
# [<OperatingSystem.UBUNTU: 'linux'>, <OperatingSystem.MACOS: 'darwin'>, <OperatingSystem.WINDOWS: 'win'>]


# To access aliases, use __members__
print(list(OperatingSystem.__members__.items()))

# [('UBUNTU', <OperatingSystem.UBUNTU: 'linux'>), ('MACOS', <OperatingSystem.MACOS: 'darwin'>),
#  ('WINDOWS', <OperatingSystem.WINDOWS: 'win'>), ('DEBIAN', <OperatingSystem.UBUNTU: 'linux'>)]

当我们不想让枚举成员出现重复的value时,可以加unique装饰器。这样声明时就会提醒报错了,比如下面这个示例。

from enum import Enum, unique

@unique
class OperatingSystem(Enum):
    UBUNTU = "linux"
    MACOS = "darwin"
    WINDOWS = "win"
    DEBIAN = "linux"

在使用枚举值时,我们有三种访问方法。点访问属性的方式,call方式,以及中括号下标方式。

from enum import Enum

class CardinalDirection(Enum):
    NORTH = "N"
    SOUTH = "S"
    EAST = "E"
    WEST = "W"

# Dot notation
CardinalDirection.NORTH

# Call notation
CardinalDirection("N")

# Subscript notation
CardinalDirection["NORTH"]

还可以枚举值的两个属性,name, value。

from enum import Enum

class Semaphore(Enum):
    RED = 1
    YELLOW = 2
    GREEN = 3

Semaphore.RED.name  # 'RED'

Semaphore.RED.value # 1

Semaphore.YELLOW.name  # 'YELLOW'

枚举类是可迭代的,我们可以使用for loop方法来迭代.

from enum import Enum

class Flavor(Enum):
    VANILLA = 1
    CHOCOLATE = 2
    MINT = 3

for flavor in Flavor:
    print(flavor)

输出结果:

Flavor.VANILLA
Flavor.CHOCOLATE
Flavor.MINT

enum类型值之间可以全用is==来比较,但不能使用">"或"<"来比较。可以使用in来判断是否一个成员是在一个enum类中。注意,两个不同的enum类,即便成员名称和值相同,使用is==来比较时也会是False。

from enum import Enum

class Semaphore(Enum):
    RED = 1
    YELLOW = 2
    GREEN = 3
    ALIAS_RED = 1


print(Semaphore.RED in Semaphore)   # True
print(Semaphore.ALIAS_RED in Semaphore)  # True

print(Semaphore.RED is Semaphore.ALIAS_RED)  # True
print(Semaphore.RED == Semaphore.ALIAS_RED)  # True

print(Semaphore.RED == 1)  # False

# 下面这行会报错:'>' not supported between instances of 'Semaphore' and 'Semaphore'
# print(Semaphore.YELLOW > Semaphore.RED)  

也正因为Enum类并未实现><操作符,所以不能使用sorted()函数。比如下面这个示例就会报错。

from enum import Enum

class Season(Enum):
    SPRING = 1
    SUMMER = 2
    AUTUMN = 3
    WINTER = 4

sorted(Season)  # TypeError: '<' not supported between instances of 'Season' and 'Season'

但我们可以使用其成员的name或value属性来进行排序。

sorted(Season, key=lambda season: season.value)
sorted(Season, key=lambda season: season.name)

我们可以在定义枚举类型时,增加一些方法,因为枚举类型也遵循正常定义class的方式。

>>> from enum import Enum

>>> class Mood(Enum):
...     FUNKY = 1
...     MAD = 2
...     HAPPY = 3
...
...     def describe_mood(self):
...         return self.name, self.value
...
...     def __str__(self):
...         return f"I feel {self.name}"
...
...     @classmethod
...     def favorite_mood(cls):
...         return cls.HAPPY
...

>>> Mood.HAPPY.describe_mood()
('HAPPY', 3)

>>> print(Mood.HAPPY)
I feel HAPPY

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>

再举一个示例:

>>> from enum import Enum

>>> class Sort(Enum):
...     ASCENDING = 1
...     DESCENDING = 2
...     def __call__(self, values):
...         return sorted(values, reverse=self is Sort.DESCENDING)
...

>>> numbers = [5, 2, 7, 6, 3, 9, 8, 4]

>>> Sort.ASCENDING(numbers)
[2, 3, 4, 5, 6, 7, 8, 9]

>>> Sort.DESCENDING(numbers)
[9, 8, 7, 6, 5, 4, 3, 2]

当我们定义枚举类时,让其同时继承int和Enum,它就具备了与数字比较的能力,即>,<==操作符。

>>> from enum import Enum

>>> class Size(int, Enum):
...     S = 1
...     M = 2
...     L = 3
...     XL = 4
...

>>> Size.S > Size.M
False
>>> Size.S < Size.M
True
>>> Size.L >= Size.M
True
>>> Size.L <= Size.M
False

>>> Size.L > 2
True
>>> Size.M < 1
False

但enum模块其实已经帮我们定义了一个IntEnum类供我们使用。

>>> from enum import IntEnum

>>> class Size(IntEnum):
...     S = 1
...     M = 2
...     L = 3
...     XL = 4
...

>>> Size.S > Size.M
False
>>> Size.S < Size.M
True
>>> Size.L >= Size.M
True
>>> Size.L <= Size.M
False

>>> Size.L > 2
True
>>> Size.M < 1
False

除此之外,还有Flag和IntFlag类型。

>>> from enum import IntFlag

>>> class Role(IntFlag):
...     OWNER = 8
...     POWER_USER = 4
...     USER = 2
...     SUPERVISOR = 1
...     ADMIN = OWNER | POWER_USER | USER | SUPERVISOR
...

>>> john_roles = Role.USER | Role.SUPERVISOR
>>> john_roles
<Role.USER|SUPERVISOR: 3>

>>> type(john_roles)
<enum 'Role'>

>>> if Role.USER in john_roles:
...     print("John, you're a user")
...
John, you're a user

>>> if Role.SUPERVISOR in john_roles:
...     print("John, you're a supervisor")
...
John, you're a supervisor

>>> Role.OWNER in Role.ADMIN
True

>>> Role.SUPERVISOR in Role.ADMIN
True

IntFlag比Flag多了一个直接与数字进行比较和加减运算的能力。

posted @   RolandHe  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示