Enumerations in python
Duplicate:
What’s the best way to implement an ‘enum’ in Python?
在Python中,公认的枚举方式是什么?
例如,现在我正在写一个游戏,希望能够移动"向上"、"向下"、"左"和"右"。我使用字符串是因为我还没有弄清楚枚举在Python中是如何工作的,所以我的逻辑中到处都是这样的东西:
1 2 3 | def move(self, direction): if direction =="up": # Do something |
我想把
更新1:python 3.4将有一个内置的设计良好的枚举库。这些值总是知道它们的名称和类型;有一个整数兼容模式,但建议新使用的默认值是singleton,与任何其他对象都不相同。
更新2:自从写了这个之后,我意识到枚举的关键测试是序列化。其他方面可以稍后重构,但如果枚举进入文件/连接,请预先问问自己,如果它是由旧/新版本反序列化的(可能支持一组不同的值),会发生什么情况…
如果您确定需要枚举,那么其他人已经回答了如何进行枚举。但让我们看看你为什么想要它们?了解动机有助于选择解决方案。
原子值-在C语言中,小数字很容易传递,字符串则不容易传递。在Python中,"up"这样的字符串非常适合多种用途。而且,任何结果只有一个数字的解决方案对于调试来说都是糟糕的!
有意义的价值观-在C语言中,你经常需要处理现有的魔法数字,只需要一些语法糖。这里不是这样的。但是,您可能希望与其他有意义的信息关联方向,例如(dx,dy)向量-更多信息见下文。
类型检查-在C中,枚举有助于在编译时捕获无效值。但python通常更喜欢牺牲编译器检查来减少输入。
自省(不存在于c枚举中)-您希望知道所有有效值。
- 完成-编辑器可以显示可能的值并帮助您键入它们。
已兑现的字符串(aka符号)
因此,在Python解决方案的浅色方面,只需使用字符串,并可能拥有所有有效值的列表/集合:
1 2 3 4 5 6 7 8 | 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"作为参数调用的。如果
在Lisp语言家族中,这种用法被称为符号-原子对象可以像数字一样简单地使用,但带有文本值。(准确地说,符号是类似于字符串的,但是一种单独的类型。但是,python通常使用常规字符串,其中lisp将使用符号。)
命名空间字符串您可以将
如果要捕获(运行时)拼写错误:
1 2 3 | UP = 'up' ... RIGHT = 'right' |
如果你想坚持输入前缀来完成任务,把上面的内容放到一个类中:
1 2 3 4 | class Directions: UP ="up" ... RIGHT ="right" |
或者只是在一个单独的文件中,使其成为一个模块。
一个模块允许懒惰的用户执行
如果每个值都有相关的有用信息/功能怎么办?"右"不只是4个任意值中的一个,它是x轴上的正方向!
如果你所做的事情是:
1 2 3 4 5 6 7 8 9 | 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 |
你真正想写的是:
1 2 3 | def move(self, direction): self.x += direction.dx * STEP self.y += direction.dy * STEP |
就这样!
因此,您希望将其放入以下两个实例中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # 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") |
或者只是上课:
1 2 3 4 5 6 7 8 9 10 11 12 | class Direction(object): pass class Up(Direction): dx = 0 dy = 1 ... class Right(Direction): dx = 1 dy = 0 |
记住,在Python中,类也是对象(不同于任何其他对象),您可以比较它们:
通常,实例可能更干净,但如果枚举的概念具有某种层次关系,有时直接用类建模非常好。
1 2 3 4 5 | class Directions: up = 0 down = 1 left = 2 right =3 |
我给了库格尔+1,但另一个更精简的选择是
1 | dirUp, dirDown, dirLeft, dirRight = range(4) |
-
-
- (一段时间过去了)
-
所以我在想……我们这里有一个明显的干法冲突,因为我们已经在lhs上指定了四项,然后在rhs上再次指定了四项。如果我们将来添加项目会发生什么?当别人加上它们时会发生什么,也许它们比我们自己更马虎?消除干冲突的一个明显方法是使用枚举本身的列表来分配它们的值:
1 2 3 4 5 6 7 8 | >>> enums = ['dirUp', 'dirDown'] >>> for v, k in enumerate(enums): ... exec(k + '=' + str(v)) ... >>> print dirDown 1 >>> print dirUp 0 |
如果你能用
这里,我们有一个新颖的想法,即使用内置的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | >>> 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 >>> |
注意,我们在
我可以在自己的代码中使用这个。感谢OP发布问题。
-
-
- (又过了一段时间)
-
贝尼·切尔尼亚夫斯基·帕斯金发表了一些非常好的评论:
- python的默认
hash() 在不同的平台上不稳定(对于持久性应用程序是危险的) - 碰撞的可能性总是存在的。
我倾向于同意这两种观点。他的建议是使用字符串本身(我真的很喜欢使用值的自记录行为)作为散列,因此代码变成以下代码(注意,我们使用集合而不是列表来强制唯一性):
1 2 3 4 5 | >>> items=('dirUp','dirDown','dirLeft','dirRight') >>> for i in items: exec('{}="{}"'.format(i,i)) >>> dirDown 'dirDown' |
将它们放在名称空间中也很简单,以避免与其他代码冲突:
1 2 3 4 5 6 | >>> class Direction(): for i in ('dirUp','dirDown','dirLeft','dirRight'): exec('{}="{}"'.format(i,i)) >>> Direction.dirUp 'dirUp' |
他提到的加密散列的长度可以在这里看到:
1 2 3 4 | >>> from hashlib import md5 >>> crypthash = md5('dirDown'.encode('utf8')) >>> crypthash.hexdigest() '6a65fd3cd318166a1cc30b3e5e666d8f' |
1 2 3 4 5 6 7 8 | >>> 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 >>> |
这是简单有效的:
1 2 3 | class Enum(object): def __init__(self, *keys): self.__dict__.update(zip(keys, range(len(keys)))) |
用途:
1 2 3 4 5 | >>> x = Enum('foo', 'bar', 'baz', 'bat') >>> x.baz 2 >>> x.bat 3 |
如果您使用的是python 2.6+,那么您可以使用namedtuple。它们的优点是具有固定数量的属性,当需要所有枚举值时,可以像使用元组一样使用它。
为了更好地控制枚举值,可以创建自己的枚举类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
声明
您的