python中的枚举

python中的枚举,python,enums,Python,Enums,重复: python中公认的枚举方法是什么 例如,目前我正在编写一个游戏,希望能够移动“上”、“下”、“左”和“右”。我之所以使用字符串,是因为我还没有弄清楚枚举在python中是如何工作的,所以我的逻辑中充斥着这样的东西: def move(self, direction): if direction == "up": # Do something 我想把“up”替换为方向。up我给了Kugel+1,但另一个更精简的选项是 class Directions:

重复:

python中公认的枚举方法是什么

例如,目前我正在编写一个游戏,希望能够移动“上”、“下”、“左”和“右”。我之所以使用字符串,是因为我还没有弄清楚枚举在python中是如何工作的,所以我的逻辑中充斥着这样的东西:

def move(self, direction):
    if direction == "up":
        # Do something

我想把
“up”
替换为
方向。up

我给了Kugel+1,但另一个更精简的选项是

class Directions:
    up = 0
    down = 1
    left = 2
    right =3
dirUp, dirDown, dirLeft, dirRight = range(4)
      • (一些时间过去了)
所以我在想…我们这里有一个明显的干违反,我们已经在LHS上指定了四个项目,然后再次在RHS上指定了四个项目。如果我们将来添加项目会发生什么?当其他人添加它们时会发生什么,也许他们比我们更草率?删除DRY冲突的一个明显方法是使用枚举列表本身来分配其值:

>>> enums = ['dirUp', 'dirDown']
>>> for v, k in enumerate(enums):
...     exec(k + '=' + str(v))
...     
>>> print dirDown
1
>>> print dirUp
0
如果您能忍受使用
exec()
进行此操作,则可以。如果没有,则使用另一种方法。无论如何,目前的讨论都是学术性的。然而,这里仍然存在一个问题。如果枚举在大量源代码中使用,而其他程序员出现并在
dirUp
dirDown
之间插入一个新值,该怎么办?这将导致痛苦,因为枚举名称和枚举本身之间的映射将是错误的。请记住,即使在最初的简单解决方案中,这仍然是一个问题

这里,我们有一个新颖的想法,即使用内置的
hash()
函数将枚举值确定为int,并使用枚举本身的文本名称来确定哈希:

>>> for k in enums:
...     exec(k + '=' + str(hash(k)))
... 
>>> dirUp
-1147857581
>>> dirDown
453592598
>>> enums = ['dirUp', 'dirLeft', 'dirDown']
>>> for k in enums:
...     exec(k + '=' + str(hash(k)))
... 
>>> dirUp
-1147857581
>>> dirDown
453592598
>>> dirLeft
-300839747
>>> 
请注意,我们在
dirUp
dirDown
之间插入了一个新值,即
dirLeft
,前两个的原始映射值没有改变

我可能会在自己的代码中使用它。感谢OP发布的问题

      • (再过一些时间)
Beni Cherniavsky Paskin发表了一些非常好的评论:

  • Python的默认值
    hash()
    在跨平台上不稳定(对于持久性应用程序来说是危险的)
  • 碰撞的可能性总是存在的
我倾向于同意这两种观点。他的建议是使用字符串本身(我非常喜欢使用值的自文档化行为)作为散列,因此代码如下(注意,我们使用集合而不是列表来强制唯一性):

为了避免与其他代码冲突,将这些代码放在命名空间中也很简单:

>>> class Direction():
        for i in ('dirUp','dirDown','dirLeft','dirRight'):
            exec('{}="{}"'.format(i,i))

>>> Direction.dirUp
'dirUp'
他提到的加密散列的长度可以在这里看到:

>>> from hashlib import md5
>>> crypthash = md5('dirDown'.encode('utf8'))
>>> crypthash.hexdigest()
'6a65fd3cd318166a1cc30b3e5e666d8f'
对象可以提供这样的命名空间:

>>> import collections
>>> dircoll=collections.namedtuple('directions', ('UP', 'DOWN', 'LEFT', 'RIGHT'))
>>> directions=dircoll(0,1,2,3)
>>> directions
directions(UP=0, DOWN=1, LEFT=2, RIGHT=3)
>>> directions.DOWN
1
>>> 

如果您使用的是Python 2.6+,那么您可以使用。它们的优点是具有固定数量的属性,当您需要所有枚举值时,可以像元组一样使用它

为了更好地控制枚举值,您可以创建自己的枚举类

def enum(args, start=0):
    class Enum(object):
        __slots__ = args.split()

        def __init__(self):
            for i, key in enumerate(Enum.__slots__, start):
                setattr(self, key, i)

    return Enum()

>>> e_dir = enum('up down left right')
>>> e_dir.up
0
>>> e_dir = enum('up down left right', start=1)
>>> e_dir.up
1
声明
\uuuuu slots\uuuuu
密封您的枚举类,无法为从具有
\uuuuu slots\uuuu
属性的类创建的对象设置更多属性


您的
Enum
类也可以基于
namedtuple
,在这种情况下,您还可以获得tuple的特性。请参见子类化
namedtuple

更新1:Python 3.4将有一个设计良好的内置接口。这些值总是知道它们的名称和类型;有一个整数兼容模式,但建议新使用的默认值为Singleton,不等于任何其他对象

更新2:写这篇文章后,我意识到枚举的关键测试是序列化。其他方面可以在以后重构,但是如果您的枚举进入文件/连接,请事先问问自己,如果它被旧/新版本(可能支持不同的值集)反序列化,会发生什么情况


如果您确定需要枚举,其他人已经回答了如何使用。 但让我们看看你为什么想要它们?了解动机将有助于选择解决方案

  • 原子值-在C语言中,小数字很容易传递,字符串则不然。 在Python中,像“up”这样的字符串非常适合多种用途。 此外,任何最终只有一个数字的解决方案对调试来说都是糟糕的

  • 有意义的值-在C语言中,您经常需要处理现有的魔法 数字,只是想要一些语法糖。这里不是这样。 但是,您可能还需要关联其他有意义的信息 方向,例如(dx,dy)矢量-更多信息如下

  • 类型检查-在C语言中,枚举有助于在编译时捕获无效值。 但是Python通常更喜欢牺牲编译器检查来减少类型

  • 内省(在C枚举中不存在)-您想知道所有有效值

    • 完成-编辑器可以显示可能的值并帮助您键入
已赎回的字符串(又名符号) 因此,在Pythonic解决方案的光明面上,只需使用字符串,并且可能有一个包含所有有效值的列表/集合:

DIRECTIONS = set(['up', 'down', 'left', 'right'])

def move(self, direction):
    # only if you feel like checking
    assert direction in DIRECTIONS
    # you can still just use the strings!
    if direction == 'up':
        # Do something
请注意,调试器会告诉您,函数是以“up”作为参数调用的。任何
direction
实际上是
0
的解决方案都比这糟糕得多

在LISP语言家族中,这种用法被称为符号——原子对象与数字一样容易使用,但带有文本值。(准确地说,符号类似字符串,只是一种单独的类型。然而,Python通常使用常规字符串,而LISP则使用符号。)

名称空间字符串 您可以将

DIRECTIONS = set(['up', 'down', 'left', 'right'])

def move(self, direction):
    # only if you feel like checking
    assert direction in DIRECTIONS
    # you can still just use the strings!
    if direction == 'up':
        # Do something
UP = 'up'
...
RIGHT = 'right'
class Directions:
    UP = "up"
    ...
    RIGHT = "right"
def move(self, direction):
    if direction == 'up':
        self.y += STEP
    elif direction == 'down':
        self.y -= STEP
    elif direction == 'left':
        self.x -= STEP
    elif direction == 'right':
        self.x += STEP
def move(self, direction):
    self.x += direction.dx * STEP
    self.y += direction.dy * STEP
# Written in full to give the idea.
# Consider using collections.namedtuple
class Direction(object):
    def __init__(self, dx, dy, name):
        self.dx = dx
        self.dy = dy
        self.name = name
    def __str__(self):
        return self.name

UP = Direction(0, 1, "up")
DOWN = Direction(0, -1, "down")
LEFT = Direction(-1, 0, "left")
RIGHT = Direction(1, 0, "right")
class Direction(object):
    pass

class Up(Direction):
    dx = 0
    dy = 1

...

class Right(Direction):
    dx = 1
    dy = 0
class Enum(object):
  def __init__(self, *keys):
    self.__dict__.update(zip(keys, range(len(keys))))
>>> x = Enum('foo', 'bar', 'baz', 'bat')
>>> x.baz
2
>>> x.bat
3