python中的枚举

时间:2009-12-28 11:16:55

标签: python enums

  

重复:
  What’s the best way to implement an ‘enum’ in Python?

什么是在python中进行枚举的公认方法?

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

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

我想用"up"

之类的内容替换Directions.up

6 个答案:

答案 0 :(得分:63)

更新1 :Python 3.4将内置精心设计的enum library。值总是知道他们的名字和类型;存在整数兼容模式,但新用途的推荐默认值是单例,与任何其他对象不相等。

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


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

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

  • 有意义的价值 - 在C中,您经常拥有来处理现有的魔法 数字,只是想要一些语法糖。情况并非如此。 但是,您可能想要关联的其他有意义的信息 方向,例如(dx,dy)向量 - 更多关于以下内容。

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

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

    • 完成 - 编辑器可以显示可能的值并帮助您输入。

Strings Redeemed(又名符号)

因此,从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将使用符号。)

Namespaced Strings

您可以将'up'优于0的想法与其他解决方案相结合。

如果你想捕捉错误(在运行时):

UP = 'up'
...
RIGHT = 'right'

如果你想坚持输入一个前缀来完成,把上面的内容放在一个类中:

class Directions:
    UP = "up"
    ...
    RIGHT = "right"

或仅在一个单独的文件中,使其成为一个模块。

模块允许懒惰用户from directions import *跳过前缀 - 无论你认为这是加号还是减号......(我个人不愿意被迫键入Directions.UP我经常使用它。

具有功能的对象

如果每个值都有与之相关的有用信息/功能,该怎么办? “右”不只是4个任意值中的一个,而是X轴上的正方向!

如果您在if中所做的事情如下:

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

请记住,在Python中,类也是对象(与任何其他对象不同),您可以比较它们:direction == Up等。

通常情况下,实例可能更干净,但如果您的枚举概念具有某种层次关系,有时直接用类对它们进行建模是非常好的。

答案 1 :(得分:43)

class Directions:
    up = 0
    down = 1
    left = 2
    right =3

答案 2 :(得分:17)

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

dirUp, dirDown, dirLeft, dirRight = range(4)
      • (一些时间过去了)

所以我在想......我们在这里有明显的DRY违规,因为我们已经在LHS上指定了四个项目,然后再在RHS上指定了四个项目。如果我们将来添加项目会怎样?当别人添加它们时会发生什么,也许它们比我们自己更邋??删除DRY违规的一种显而易见的方法是使用枚举列表本身来分配它们的值:

>>> enums = ['dirUp', 'dirDown']
>>> for v, k in enumerate(enums):
...     exec(k + '=' + str(v))
...     
>>> print dirDown
1
>>> print dirUp
0

如果您可以使用exec()来锻炼,那么很好。如果没有,那么使用另一种方法。无论如何,目前的讨论都是学术性的。但是,这里仍然存在问题。如果在大量源代码中使用枚举,并且其他一些程序员出现并在dirUpdirDown之间插入新值,该怎么办?这将导致痛苦,因为枚举名称和枚举本身之间的映射将是错误的。请记住,即使在最初的简单解决方案中,这仍然是一个问题。

在这里,我们有一个新颖的想法,即使用内置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
>>> 

请注意,我们在dirUpdirDown之间插入了一个新值,即dirLeft,前两个的原始映射值未更改

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

      • (还有一些时间过去了)

Beni Cherniavsky-Paskin发表了一些非常好的评论:

  • Python的默认hash()在各个平台上都不稳定(对持久性应用程序来说很危险)
  • 始终存在碰撞的可能性。

我倾向于同意这两个观察结果。他的建议是使用字符串本身(我真的喜欢使用值的自我记录行为)作为哈希,因此代码变为以下(注意我们使用集合而不是列表,以强制唯一性):< / p>

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

将这些放在命名空间中也是微不足道的,以避免与其他代码冲突:

>>> 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'

答案 3 :(得分:14)

collections.namedtuple对象可以提供这样的命名空间:

>>> 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
>>> 

答案 4 :(得分:13)

这很简单有效:

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

答案 5 :(得分:7)

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

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

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

声明__slots__密封您的Enum类,不能再为从具有__slots__属性的类创建的对象设置属性。

您的Enum课程也可以namedtuple为基础,在这种情况下,您还可以获得元组的功能。有关子类化namedtuple

的信息,请参阅namedtuple docs