enum — 枚举

前情提示: 测试代码中,右尖括号( >
)表示命令行中输入的命令; 单独一行并以井字符( #
)开头的为输出内容; 库的导入仅在本文的第一个测试代码中展现,其他代码块均省略库的导入代码。

  • 系统类型
    : Windows 10
  • python 版本
    : Python 3.9.0

枚举是一组符号名称(枚举成员)的集合,枚举成员应该是唯一的、不可变的。

enum
模块将分为三个部分解析,第一部分主要介绍枚举的特性和 Enum
类,第二部分将介绍 Enum
类的三个变种类及自定义类,第三部分将更深入的了解枚举。也可通过传送门查看微信公众号文章。

传送门

enum — 枚举(一)

enum — 枚举(二)

enum — 枚举(三)

第一部分

创建一个 Enum

枚举是使用 class
语法来创建的,这使得它们易于读写。但还是有一种以使用函数形式的创建方式,这个留到下文再讲,现在主要来说明一下使用 class
语法的创建方式。

import enum
class Test(enum.Enum):
A = 1
B = 'b'
复制代码

上面的代码创建了一个枚举, class Test
是一个 枚举
,其中 Test
就是枚举对象的名称。

A = 1
B = 'b'
这些等号两边的整体被称为 枚举成员
,等号左边的被称为枚举成员的名称,枚举通常是用来表示常量的,所以枚举成员的名称一般使用大写形式。等号右边的被称为枚举成员的值,枚举成员的值可以是任意类型,如 int
str
等等,只需要注意枚举成员的值在 枚举对象中是唯一的。

虽然创建枚举时使用了 class
语法,但是枚举与普通的 python
类不同,可以打印一些信息来进行对比:

'''枚举'''
class Test(enum.Enum):
A = 1
print(Test)                      # 打印枚举
# <enum 'Test'>
print(Test.A)                    # 打印枚举成员本身
# Test.A
print(repr(Test.A))              # 打印枚举成员在解释器中的形式
# <Test.A: 1>
print(type(Test.A))              # 打印枚举成员类型
# <enum 'Test'>
print(isinstance(Test.A, Test))  # 判断枚举成员类型是否有枚举类型一致
# True
'''普通的 python 类'''
class Common:
A = 1
print(Common)                    # 打印类
# <class '__main__.Common'>
print(Common.A)                  # 打印类属性的值
# 1
print(repr(Common.A))            # 打印类属性的值在解释器中的形式
# 1
print(type(Common.A))            # 打印类属性的值的类型
# <class 'int'>
print(isinstance(Common.A, Test))  # 判断类属性的值的类型是否有类的类型一致
# False
复制代码

首先,枚举创建出的对象自成一种类型,类型名就是枚举的名称(类名)。枚举的枚举成员(类的属性)被创建成了一个对象,并且类型就是所属枚举的类型。

枚举可以按照定义顺序进行迭代,并且枚举成员是不可变的,是可哈希的,因此字典中的键可以使用枚举成员。

class Test(enum.Enum):
A = 1
test_dict = {}
for i in Test:
test_dict[i] = 'value.' + str(i)
print(test_dict)
# {<Test.A: 1>: 'value.Test.A', <Test.B: 'b'>: 'value.Test.B', <Test.C: 'c'>: 'value.Test.C'}
print(test_dict[Test.A])
# value.Test.A
复制代码

那么怎么获取枚举成员呢,在枚举中有很多种访问方式:

class Test(enum.Enum):
A = 1
print(Test(1))       # 程序化访问
# Test.A
print(Test['A'])     # 条目访问
# Test.A
print(Test.A.value)  # 枚举成员的 value 属性
# 1
print(Test.A.name)   # 枚举成员的 name 属性
# A
复制代码

枚举中不允许有相同的枚举成员,但是允许枚举成员有同样的值,若查找的值有多个枚举成员,那么会按定义顺序返回第一个。

'''枚举成员有相同的名称'''
class Test(enum.Enum):
A = 1
A = 2
# TypeError: Attempted to reuse key: 'A'
'''枚举成员有相同的值'''
class Test(enum.Enum):
A = 1
B = 1
print(Test(1))
# Test.A
复制代码

enum
模块中定义了一个装饰器 — @enum.unique
,可以强制规定枚举成员的值也必须是唯一的。

@enum.unique
class Test(enum.Enum):
A = 1
B = 1
# ValueError: duplicate values found in <enum 'Test'>: B -> A
复制代码

若枚举成员的值不重要或者其他不需要一一指定枚举成员的场景下,可以使用 enum.auto()
方法来替代输入的值。

class Test(enum.Enum):
A = enum.auto()
B = enum.auto()
C = 1
print(repr(Test.A))
# <Test.A: 1>
print(repr(Test.B))
# <Test.B: 2>
print(repr(Test.C))
# <Test.A: 1>
print(list(Test))
# [<Test.A: 1>, <Test.B: 2>]
复制代码

当使用 enum.auto()
来定义枚举成员的值时,再次使用 int
类型的值时,最后得到的结果可能会和预想的不太一样。

enum.auto()
函数的返回值是由 _generate_next_value_()
函数决定的,默认情况下,此函数是根据最后一个 int
类型的枚举成员的值增加 1
。此函数是可以重载的,重载时,方法定义必须在任何成员之前。

'''先定义一个值为 int 类型的枚举成员, 再使用 auto() 函数'''
class Test(enum.Enum):
A = 2
B = enum.auto()
C = enum.auto()
print(list(Test))
# [<Test.A: 2>, <Test.B: 3>, <Test.C: 4>]
'''定义一个值为非 int 类型的枚举成员, 再使用 auto() 函数'''
class Test(enum.Enum):
A = 'b'
B = enum.auto()
C = enum.auto()
print(list(Test))
# [<Test.A: 'b'>, <Test.B: 1>, <Test.C: 2>]
'''重载 _generate_next_value_()'''
class Test(enum.Enum):
def _generate_next_value_(name, start, count, last_values):
return name  # 返回枚举成员的名字
A = enum.auto()
B = enum.auto()
print(list(Test))
# [<Test.A: 'A'>, <Test.B: 'B'>]
复制代码

在介绍 enum.auto()
函数时,测试示例中,枚举对象转换为 list
类型时少了一个枚举成员,这是因为少的那个枚举成员与之前的枚举成员的值是一样的,这个枚举成员被认为是一个别名,当对枚举成员迭代时,不会给出别名。

class Test(enum.Enum):
A = 1
B = 1
print(list(Test))
# [<Test.A: 1>]
复制代码

而特殊属性 __members__
是一个从名称到成员的只读有序映射。它包含枚举中所有的枚举成员,但是在迭代时,因为别名的原因,指向的枚举成员还是该值第一个定义的枚举成员。

class Test(enum.Enum):
A = 1
B = 1
print(dict(Test.__members__))
# {'A': <Test.A: 1>, 'B': <Test.A: 1>}
复制代码

小栗子,找出枚举中所有的别名:

class Test(enum.Enum):
A = 1
B = 1
C = 2
D = 2
print([名称 for 名称, 枚举成员 in Test.__members__.items() if 枚举成员.name != 名称])
# ['B', 'D']
复制代码

在枚举中,不仅仅只可以定义一些枚举成员,枚举也属于 python
的类,是可以拥有普通方法和特殊方法的,这里列举一个文档上的示例:

class Mood(enum.Enum):
FUNKY = 1
HAPPY = 3
def describe(self):
return self.name, self.value
def __str__(self):
return 'my custom str! {0}'.format(self.value)
@classmethod
def favorite_mood(cls):
return cls.HAPPY
print(Mood.favorite_mood())
# my custom str! 3
print(Mood.HAPPY.describe())
# ('HAPPY', 3)
print(str(Mood.FUNKY))
# my custom str! 1
复制代码

但是枚举还是有一些限制,以单下划线开头和结尾的名称是由枚举保留而不可使用,例外项是包括特殊方法成员 ( __str__()
__add__()
等),描述符 (方法也属于描述符) 以及在 _ignore_
中列出的变量名。

在介绍 _generate_next_value_()
函数时,为了重载这个函数,我们创建了一个基于 enum.Enum
的枚举,可以成为 枚举A
,重载了 _generate_next_value_()
函数,然后又创建了一个基于 枚举A
的枚举。这样的定义是被允许的,在 enum
模块的规定下,一个新的 Enum
类必须基于一个 Enum
类,至多一个实体数据类型以及出于实际需要的任意多个基于 object
mixin
类。 这些基类的顺序为:

class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
复制代码

另外,没有定义枚举成员的枚举才能被其他枚举继承,否则将会报错:

class A(enum.Enum):
AA = 1
class B(A):
BB = 2
# TypeError: B: cannot extend enumeration 'A'
复制代码

第二部分

IntEnum

IntEnum
类是 Enum
类的子类,也是 int
的子类。 IntEnum
的枚举成员的值必须是 int
类型,填写其他类型会报错。

class IntTest(enum.IntEnum):
A = 1
B = 'b'
# ValueError: invalid literal for int() with base 10: 'b'
复制代码

除此之外, IntEnum
类和 Enum
类最大的区别在与比较时的差异,前文中并没有介绍 Enum
类比较功能的特性,这里先说一下 Enum
类的比较:

枚举成员只与自己本身和别名相等,枚举成员与非枚举值比较永远不相等,枚举成员只能无法进行排序比较。

class TestA(enum.Enum):
A = 1
B = 1
class TestB(enum.Enum):
A = 1
B = 2
print(TestA.A is TestB.A)  # 不同枚举的同名同值的枚举成员的 is 比较
# False
print(TestA.A is TestA.B)  # 同一枚举的同值枚举成员 is 比较(别名)
# True
print(TestA.A == TestB.A)  # 不同枚举的同名同值的枚举成员的 == 比较
# False
print(TestA.A == TestA.B)  # 同一枚举的同值枚举成员 == 比较(别名)
print(TestA.A == 1)  # 与 int 类型比较
# False
print(TestA.A < TestA.B)  # 排序比较
# Traceback (most recent call last):
#   File "e:\project\test\test.py", line 15, in <module>
#     print(TestA.A < TestA.B)
# TypeError: '<' not supported between instances of 'TestA' and 'TestA'
复制代码

IntEnum
的枚举成员可以直接与 int
类型的值比较,也支持排序比较,并且不同枚举的同名同值的枚举成员使用 ==
比较时也相等。

class IntTestA(enum.IntEnum):
A = 1
B = 1
class IntTestB(enum.IntEnum):
A = 1
B = 2
print(IntTestA.A is IntTestB.A)  # 不同枚举的同名同值的枚举成员的 is 比较
# False
print(IntTestA.A is IntTestA.B)  # 同一枚举的同值枚举成员 is 比较(别名)
# True
print(IntTestA.A == IntTestB.A)  # 不同枚举的同名同值的枚举成员的 == 比较
# True
print(IntTestA.A == IntTestA.B)  # 同一枚举的同值枚举成员 == 比较(别名)
# True
print(IntTestA.A == 1)  # 与 int 类型比较
# True
print(IntTestA.A < IntTestB.B)  # 排序比较
# True
复制代码

IntEnum
在其他方面的行为类似于 int
类型的数据:

class IntTestA(enum.IntEnum):
A = 1
B = 2
print(['a', 'b', 'c'][IntTestA.A])  # 枚举成员作为索引
# b
print([i for i in range(IntTestA.B)])  # 枚举成员作为 range() 函数的参数
# [0, 1]
复制代码

Flag

Flag
类是 Enum
类的子类, Flag
类与 Enum
类的差别就是 Flag
枚举成员可以使用按位运算符( &
|
^
~
)。

class TestFlag(enum.Flag):
A = 1
B = 2
C = A | B
print(list(TestFlag))  # 所有枚举成员
# [<TestFlag.A: 1>, <TestFlag.B: 2>, <TestFlag.C: 3>]
print(repr(TestFlag.A | TestFlag.B))  # 枚举成员A 与 B 按位或
# <TestFlag.C: 3>
print(repr(TestFlag.C))  # 枚举成员 C
# <TestFlag.C: 3>
print(1 | 2)  # 1 与 2 按位或
# 3
复制代码

Flag
类的枚举成员可以在定义时使用按位运算符,也可以在使用枚举成员时使用按位运算符, 并且如果得到的值与枚举中的枚举成员的值相等时,最后得到的结果就是值与结果相同的枚举成员。那么如果在使用枚举成员时使用按位运算符,最后的结果在枚举中没有枚举成员的值与之相等呢?

class TestFlag(enum.Flag):
A = 1
B = 2
print(repr(TestFlag.A | TestFlag.B))  # 枚举成员A 与 B 按位或
# <TestFlag.B|A: 3>
print(type(TestFlag.A | TestFlag.B))  # 查看结果的类型
# <enum 'TestFlag'>
复制代码

Flag
类的枚举成员之间使用按位运算符之后, 如果枚举中有相同值的枚举成员时得到那个枚举成员,否则将获取一个新的枚举成员。

IntFlag

IntFlag
类是 Flag
Int
类的子类。其实, IntFlag
类更像是可以使用按位运算符的 IntEnum
。但在某些方面 IntFlag
还是有自己独有的特点。

首先, IntFlag
的枚举成员的值只能是数字,这点与 IntEnum
类相同。 IntFlag
的枚举成员在定义时可以使用按位运算符,在使用枚举成员时也可以使用按位运算符, 得到的结果还是枚举成员,这点与 Flag
类相同。

class TestIntFlag(enum.IntFlag):
A = 1
B = 2
C = A | B
print(repr(TestIntFlag.A | TestIntFlag.B))
# <TestIntFlag.C: 3>
复制代码

因为继承了 Int
类, IntFlag
类的枚举成员也可以像 IntEnum
类的枚举成员那样进行运算,作索引值等等。但是, IntFlag
类的枚举成员进行除按位运算以外的其他运算都将导致失去 IntFlag
类的枚举成员的资格。

class TestIntFlag(enum.IntFlag):
A = 1
B = 2
print(TestIntFlag.A + TestIntFlag.B)  # 加法运算
# 3
print(['a', 'b', 'c', 'd'][TestIntFlag.A])  # 索引值
# b
复制代码

自定义类

当以上枚举类都无法满足需求时,可以修改枚举类中的魔法方法来改变某些特性或功能。

__new__()
魔法方法在每个枚举成员定义前调用,可以修改枚举成员的值和设置自定义属性。

'''修改枚举成员定义的值'''
class Test(enum.Enum):
A = 'aaa'
B = 'bbb'
def __new__(cls, value):
obj = object.__new__(cls)
obj._value_ = str(value) + '...'
return obj
print(list(Test))
# [<Test.A: 'aaa...'>, <Test.B: 'bbb...'>]
复制代码

__init__()
魔法方法在枚举成员定义后调用,可实现的功能与 __new__()
魔法方法基本一致。这里官方文档中标明出了一点,虽然 __init__()
魔法方法可以定义枚举成员的值,但是不允许使用 __init__()
魔法方法定义枚举成员的值。

'''被官方文档禁止的操作, 修改枚举成员的值'''
class Test(enum.Enum):
A = 'aaa'
B = 'bbb'
def __init__(self, pantone='unknown'):
self._value_ = str(self.value) + '...'
print(list(Test))
# [<Test.A: 'aaa...'>, <Test.B: 'bbb...'>]
复制代码

__repr__()
魔法方法的重定义,用来修改使用 repr()
函数时返回的内容:

'''使用 repr() 函数时, 不展示枚举成员的值'''
class TestA(enum.Enum):
A = enum.auto()
B = enum.auto()
def __repr__(self):
return '<%s.%s>' % (self.__class__.__name__, self.name)
print(repr(TestA.A))
# <TestA.A>
print(TestA.A.value)  # 正常获取枚举成员的值
# 1
复制代码

__repr__()
魔法方法类似, __str__()
__add__()
__format__()
__bool__()
等等魔法方法都是可以重新定义的。

下面是官方文档中的两个有趣的小例子:

'''一个有序枚举,它不是基于 IntEnum,因此保持了正常的 Enum 不变特性'''
class OrderedEnum(enum.Enum):
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
class Grade(OrderedEnum):
A = 5
B = 4
C = 3
D = 2
F = 1
print(Grade.C < Grade.A)
# True
'''如果发现重复的成员名称则将引发错误而不是创建别名'''
class DuplicateFreeEnum(enum.Enum):
def __init__(self, *args):
cls = self.__class__
if any(self.value == e.value for e in cls):
a = self.name
e = cls(self.value).name
raise ValueError(
"aliases not allowed in DuplicateFreeEnum:  %r --> %r"
% (a, e))
class Color(DuplicateFreeEnum):
RED = 1
GREEN = 2
BLUE = 3
GRENE = 2
# ValueError: aliases not allowed in DuplicateFreeEnum:  'GRENE' --> 'GREEN'
复制代码

第三部分

不重要的枚举成员的值

在某些场景中,人们并不关心枚举成员的值是什么,可以使用下列几种方法定义枚举:

enum.auto()
object()
__new__()

看完以上的列举,这种实现这种需求并不局限于上面几种方法,只要最终枚举成员的值不重复即可,比如自定义一个随机生成字符串的函数。

import enum
import pickle  # 封存
'''enum.auto()'''
class TestA(enum.Enum):
A = enum.auto()
B = enum.auto()
'''object()'''
class TestB(enum.Enum):
A = object()
B = object()
'''描述字符串'''
class TestC(enum.Enum):
A = '字母A'
B = '字母B'
'''元组, 并自定义 __new__()'''
class TestD(enum.Enum):
A = ()
B = ()
def __new__(cls, *args):
'''获取当前枚举成员数并加一, 将结果赋值给新定义的枚举成员'''
value = len(cls.__members__) + 1
obj = object.__new__(cls)  # 必须创建一个枚举成员对象
obj._value_ = value
return obj  # 必须返回一个枚举成员对象
print(list(TestA))
# [<TestA.A: 1>, <TestA.B: 2>]
print(list(TestB))
# [<TestB.A: <object object at 0x0000023986178210>>, <TestB.B: <object object at 0x0000023986178220>>]
print(list(TestC))
# [<TestC.A: '字母A'>, <TestC.B: '字母B'>]
print(list(TestD))
# [<TestD.A: 1>, <TestD.B: 2>]
复制代码

封存

枚举可以被封存和解封:

class Test(enum.Enum):
A = enum.auto()
B = enum.auto()
封存的枚举成员A = pickle.dumps(Test.A)
解封的枚举成员A = pickle.loads(封存的枚举成员A)
print(封存的枚举成员A)
# b'\x80\x04\x95\x1b\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04Test\x94\x93\x94K\x01\x85\x94R\x94.'
print(解封的枚举成员A)
# Test.A
print(Test.A is 解封的枚举成员A)  # 解封后的数据与原数据是否一致
# True
复制代码

功能性API

Enum
类属于可调用对象,也就是说 Enum
类可以像函数一样被调用:

Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
参数:
value: str, 枚举的名称
names: 枚举成员们的名称, 可使用多种方式设置枚举成员
module: 关键字参数, str 或 None, 指明所在的模块
qualname: 关键字参数, str, 指明所在的模块中的具体位置
type: 关键字参数, 指明枚举类的类型
start: 关键字参数, int, 枚举成员的起始值, 默认为 1
返回值:
枚举
复制代码

names
参数可以传入一个以空格或逗号隔开的字符串,分隔的子字符串就是枚举成员的名称,各枚举成员的值按顺序自增,起始值由 start
参数设置,起始值默认为 1
names
参数也可以传入迭代器或映射。

'''以空格分隔'''
TestA = enum.Enum('TestEnum', 'A B C D')
print(list(TestA))
# [<TestEnum.A: 1>, <TestEnum.B: 2>, <TestEnum.C: 3>, <TestEnum.D: 4>]
'''以空格分隔, 设置起始值为10'''
TestB = enum.Enum('TestEnum', 'A B C D', start=10)
print(list(TestB))
# [<TestEnum.A: 10>, <TestEnum.B: 11>, <TestEnum.C: 12>, <TestEnum.D: 13>]
'''传入列表设置枚举成员, 枚举成员的值按顺序自增, 起始值由 start 参数设置'''
TestC = enum.Enum('TestEnum', ['A', 'B', 'C', 'D'])
print(list(TestC))
# [<TestEnum.A: 1>, <TestEnum.B: 2>, <TestEnum.C: 3>, <TestEnum.D: 4>]
'''传入列表, 并且元素格式为 ('名称', '值')'''
TestD = enum.Enum('TestEnum', [('A', 11), ('B', 22), ('C', 33), ('D', 44)])
print(list(TestD))
# [<TestEnum.A: 11>, <TestEnum.B: 22>, <TestEnum.C: 33>, <TestEnum.D: 44>]
'''传入映射, 格式为 {名称: 值}'''
TestE = enum.Enum('TestEnum', {'A': 11, 'B': 22, 'C': 33, 'D': 44})
print(list(TestE))
# [<TestEnum.A: 11>, <TestEnum.B: 22>, <TestEnum.C: 33>, <TestEnum.D: 44>]
复制代码

module
参数需要传入一个模块,为了在进行封存操作时成功执行。若指定一个错误的模块,那么封存操作将报错。若参数值若设置为 None
__name__
最终指向的模块均为当前文件 __main__

'''module 参数默认为 None'''
TestA = enum.Enum('TestA', 'AAA BBB CCC')
print(pickle.loads(pickle.dumps(TestA)))  # 封存+解封
# <enum 'TestA'>
'''module 参数设置为 __name__'''
TestB = enum.Enum('TestB', 'AAA BBB CCC', module=__name__)
print(pickle.loads(pickle.dumps(TestB)))
# <enum 'TestB'>
'''module 参数设置为一个不存在的模块'''
TestD = enum.Enum('TestD', 'AAA BBB CCC', module='aaa')
print(pickle.loads(pickle.dumps(TestD)))
# _pickle.PicklingError: Can't pickle <enum 'TestD'>: import of module 'aaa' failed
复制代码

qualname
参数是指明在模块中的具体位置,同 module
参数类似,设置这个参数的目的也是为了封存操作, pickle
协议版本 4
在某些情况下需要依赖设置的具体位置。

布尔值

枚举类的布尔值总是 True
,枚举中的枚举成员的布尔值也总是 True
。 枚举成员的布尔值与枚举成员的值无关,如果想要以枚举成员的值来计算布尔值,可以修改 __bool__()
魔法方法。

'''不修改 __bool__() 魔法方法的枚举类'''
class Test(enum.Enum):
A = 0
B = None
print(bool(Test))
# True
print(bool(Test.A))
# True
print(bool(Test.B))
# True
'''修改 __bool__() 魔法方法的枚举类'''
class Test(enum.Enum):
A = 0
print(bool(Test.A))
# False
复制代码

支持枚举类使用的属性

__members__
是一个 member_name:member
条目的只读有序映射。得到的结果是以 {名称: 枚举成员对象}
为结构的字典。

class Test(enum.Enum):
A = 1
B = 2
C = 1
print(Test.__members__)
# {'A': <Test.A: 1>, 'B': <Test.B: 2>, 'C': <Test.A: 1>}
复制代码

支持枚举成员使用的属性

  • _name_
    _value_
    分别返回枚举成员的名称和值;
  • _generate_next_value_
    在功能性API中被使用,为枚举成员获取适当的值,也可被打印;
  • _missing_
    作用是当未发现某个值时所使用的查找函数;
  • _ignore_
    是一个名称列表,结果可以是 list
    ,也可以是 str
    ,不会转化为枚举成员,并将从最终类中被移除;
  • _order_
    Python2
    的遗留属性,在 Python2
    中负责记录枚举成员的定义顺序;
'''可直接打印的属性'''
class Test(enum.Enum):
A = 1
B = 2
C = 1
print(Test.A._name_)
# 'A'
print(Test.A._value_)
# 1
print(Test.A._generate_next_value_)
# <bound method Enum._generate_next_value_ of <Test.A: 1>>
'''_ignore_的用法'''
class Period(enum.Enum):
_ignore_ = 'Period i'
Period = vars()
for i in range(3):
Period['obj_%d' % i] = i
print(list(Period))
# [<Period.obj_0: 0>, <Period.obj_1: 1>, <Period.obj_2: 2>]
复制代码

参考资料

官方文档: docs.python.org/zh-cn/3/lib…

源代码: Lib/enum.py

稀土掘金
我还没有学会写个人说明!
上一篇

中外科学家合作新识别月球上近11万个撞击坑

下一篇

光荣发布《赛马大亨9:2021》模拟有马纪念赛演示

你也可能喜欢

评论已经被关闭。

插入图片