在本文中, Nick Coghlan讨论了PEP 435Enum类型的一些设计决策,以及如何EnumMeta将其子类化以提供不同的Enum体验。
Enum
EnumMeta
但是,我给出的建议(我是stdlib的主要Enum作者)关于使用元类的建议是,在没有充分好的理由的情况下不应该这样做- 例如,无法使用类装饰器或专用工具来完成所需的工作隐藏任何丑陋的功能;而在我自己的工作,我已经能够做到我需要什么简单的使用__new__,__init__在创建时,和/或正常类/实例方法Enum类:
__new__
__init__
Enum 具有属性
处理失踪成员
不是Enum成员的类常量
考虑到所有这些,我什么时候需要摆弄EnumMeta自己?
首先,查看不进行子类化时所需的代码EnumMeta:
stdlib方式
from enum import Enum import json class BaseCountry(Enum): def __new__(cls, record): member = object.__new__(cls) member.country_name = record['name'] member.code = int(record['country-code']) member.abbr = record['alpha-2'] member._value_ = member.abbr, member.code, member.country_name if not hasattr(cls, '_choices'): cls._choices = {} cls._choices[member.code] = member.country_name cls._choices[member.abbr] = member.country_name return member def __str__(self): return self.country_name Country = BaseCountry( 'Country', [(rec['alpha-2'], rec) for rec in json.load(open('slim-2.json'))], )
该aenum方法 1 2
aenum
from aenum import Enum, MultiValue import json class Country(Enum, init='abbr code country_name', settings=MultiValue): _ignore_ = 'country this' # do not add these names as members # create members this = vars() for country in json.load(open('slim-2.json')): this[country['alpha-2']] = ( country['alpha-2'], int(country['country-code']), country['name'], ) # have str() print just the country name def __str__(self): return self.country_name
上面的代码适合一次性枚举-但是如果从JSON文件创建枚举对您来说很普遍怎么办?想象一下您是否可以这样做:
class Country(JSONEnum): _init_ = 'abbr code country_name' # remove if not using aenum _file = 'some_file.json' _name = 'alpha-2' _value = { 1: ('alpha-2', None), 2: ('country-code', lambda c: int(c)), 3: ('name', None), }
如你看到的:
_file
_name
_value
_init_
JSON数据摘自https://github.com/lukes/ISO-3166-Countries-with-Regional-Codes- 以下是简短摘录:
[{“ name”:“ Afghanistan”,“ alpha-2”:“ AF”,“国家/地区代码”:“ 004”}, {“ name”:“ÅlandIslands”,“ alpha-2”:“ AX”,“国家/地区代码”:“ 248”}, {“名称”:“阿尔巴尼亚”,“ alpha-2”:“ AL”,“国家代码”:“ 008”}, {“名称”:“阿尔及利亚”,“ alpha-2”:“ DZ”,“国家/地区代码”:“ 012”}]
[{“ name”:“ Afghanistan”,“ alpha-2”:“ AF”,“国家/地区代码”:“ 004”},
{“ name”:“ÅlandIslands”,“ alpha-2”:“ AX”,“国家/地区代码”:“ 248”},
{“名称”:“阿尔巴尼亚”,“ alpha-2”:“ AL”,“国家代码”:“ 008”},
{“名称”:“阿尔及利亚”,“ alpha-2”:“ DZ”,“国家/地区代码”:“ 012”}]
这是JSONEnumMeta课程:
JSONEnumMeta
class JSONEnumMeta(EnumMeta): @classmethod def __prepare__(metacls, cls, bases, **kwds): # return a standard dictionary for the initial processing return {} def __init__(cls, *args , **kwds): super(JSONEnumMeta, cls).__init__(*args) def __new__(metacls, cls, bases, clsdict, **kwds): import json members = [] missing = [ name for name in ('_file', '_name', '_value') if name not in clsdict ] if len(missing) in (1, 2): # all three must be present or absent raise TypeError('missing required settings: %r' % (missing, )) if not missing: # process name_spec = clsdict.pop('_name') if not isinstance(name_spec, (tuple, list)): name_spec = (name_spec, ) value_spec = clsdict.pop('_value') file = clsdict.pop('_file') with open(file) as f: json_data = json.load(f) for data in json_data: values = [] name = data[name_spec[0]] for piece in name_spec[1:]: name = name[piece] for order, (value_path, func) in sorted(value_spec.items()): if not isinstance(value_path, (list, tuple)): value_path = (value_path, ) value = data[value_path[0]] for piece in value_path[1:]: value = value[piece] if func is not None: value = func(value) values.append(value) values = tuple(values) members.append( (name, values) ) # get the real EnumDict enum_dict = super(JSONEnumMeta, metacls).__prepare__(cls, bases, **kwds) # transfer the original dict content, _items first items = list(clsdict.items()) items.sort(key=lambda p: (0 if p[0][0] == '_' else 1, p)) for name, value in items: enum_dict[name] = value # add the members for name, value in members: enum_dict[name] = value return super(JSONEnumMeta, metacls).__new__(metacls, cls, bases, enum_dict, **kwds) # for use with both Python 2/3 JSONEnum = JSONEnumMeta('JsonEnum', (Enum, ), {})
一些注意事项:
JSONEnumMeta.__prepare__ 返回正常 dict
JSONEnumMeta.__prepare__
dict
EnumMeta.__prepare__用于获取的实例_EnumDict-这是获取实例的正确方法
EnumMeta.__prepare__
_EnumDict
带下划线的键将被传递给实数_EnumDict优先,因为在处理枚举成员时可能需要它们
枚举成员的顺序与文件中的顺序相同
1披露:我是Python stdlibEnum,enum34backport和Advanced Enumeration(aenum) 库的作者。
enum34
2这需要aenum 2.0.5+。
aenum 2.0.5+
3键是数字键,可以在需要多个值时按顺序保留多个值Enum。