Python中实现类似Java Enum

早上起来用web.py做东西的时候出现这么个问题,页面渲染的时候要做个下拉框,下拉框的内容是各种状态,类似这样:

<select name='edu_grade'>
  <option value='1'>自学成才</option>
  <option value='2'>专科</option>
  <option value='3'>学士</option>
  <option value='4'>硕士</option>
  <option value='5'>博士</option>
  <option value='6'>圣斗士</option>
</select>
在java中这事很好做,通常都会是写一个enum,直接调values全部渲染出来即可,而且这个enum在整个系统中可以很好的复用,无论是页面渲染还是DTO都很方便。
public enum EduGrade {
   StudyBySelf(0, '自学成才'),
   ...
   ;
   private final int code;

   private final String name;

   EduGrade(int code, String name){
      this.code = code;
      this.name = name;
   }

   public int getCode(){
      return this.code;
   }

   public String getName() {
      return this.name;
   }
}
JSTL渲染也很简单:
<c:forEach items='edu_grades' var='g'>
  <option value='${g.code}'>${g.name}</option>
</c:forEach>
Python中现在没有这样语法级别的枚举支持,该怎么办呢?有这么几种Hack的方案

使用namedtuple

python中标准的tuple使用数字索引来引用元素,collections库又对tuple进行了一系列扩展,搞出了个namedtuple, 就是不是用数字索引来引用元素了,而是使用字符串名称来引用其中的元素。

namedtuple与普通的tuple不同的是,每个namedtuple都有自己的类型,需要先通过collections.namedtuple函数来创建类型:

EduGrade = collections.namedtuple('EduGrade', 'code name')
然后才能基于这种类型来创建具体的枚举:
study_by_self = EduGrade(code=1, name=u'自学成才')
看上去不错,每个常量有了自己的更多扩展属性,而不是跟以前那样只有一个值,但是还不能跟java的enum相媲美,没有类似于values这样返回集合的方法,这就需要我们自己去维护这么一个容器。

使用元类

其实我们可以发现,namedtuple这东西其实跟Python语言自身提供的元类是基本类似的,那我们也可以用元类来做这件事:

def create_enum(**kws):
    Enum =  type('Enum', (object,), kws)
    Enum.values = staticmethod(lambda:kws)
    return Enum
    
Test = create_enum(one=1, two=2, three=3)
for key in sorted(Test.values(), key=lambda x:Test.__dict__[x]):
    print Test.__dict__[key]

就是这样,通过工厂函数来构建一个元类,类的属性都作为枚举值,另外额外增加了一个values方法,注意一定要使用staticmethod进行包装,否则类似Enum.values = lambda:kws这样就不是类方法了,只能通过对象去调用,也就是第一个参数必须是self

两者结合

这样hack无论是从创建还是values遍历看起来都很优雅,但是还有一个问题,每个枚举都是参数的形式,像上面那种,one=1, two=2, three=3只能携带一个枚举相关的值,不过这个问题我们前面使用namedtuple已经解决了,可以把这两种方案结合起来:

1. 枚举值使用namedtuple来做,方便多个属性的枚举

2. 枚举常量使用元类进行维护和遍历。

最后就是这样:

import collections

def create_enum(key_field=None, **kws):
    Enum =  type('Enum', (object,), kws)
    Enum.values = staticmethod(lambda:kws)
    if key_field:
        Enum.key_map = {getattr(kws[key], key_field):kws[key] for key in kws}
    return Enum

EduGrade = collections.namedtuple('EduGrade', 'code name')

study_by_self = EduGrade(code=1, name='自学成才')
zhuanke = EduGrade(code=2, name='专科')
xueshi = EduGrade(code=3, name='学士')
shuoshi = EduGrade(code=4, name='硕士')

EduGradeEnum = create_enum(key_field='code', study_by_self=study_by_self, zhuanke=zhuanke, 
                           xueshi=xueshi, shuoshi=shuoshi)
for key in sorted(EduGradeEnum.values(), key=lambda x:EduGradeEnum.__dict__[x].code):
    value = EduGradeEnum.__dict__[key]
    print '{value.code} -> {value.name}'.format(value=value)

grade = EduGradeEnum.key_map[1]
print grade
值的注意的是我这里增加了一个key_field参数,这里主要是用于反向映射枚举,我们在数据库中保存的一般只能是1,2,3这些常量值,这里指定常量值的属性名称,那么就可以通过key_map这个枚举类属性进行反向映射

福音

Hack虽然能解决问题,但是终归是别扭的,在Python 3.4中增加了枚举支持,详细可以看http://www.python.org/dev/peps/pep-0435/ 不过用上Python3还有很长的路要走啊……最起码我这边是。

你可能感兴趣的:(Python中实现类似Java Enum)