早上起来用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的方案
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还有很长的路要走啊……最起码我这边是。