Is it possible to override __new__ in an enum to parse strings to an instance?
我想把字符串解析成python枚举。通常可以实现一个解析方法来实现这一点。几天前,我发现了一个新的方法,它可以根据给定的参数返回不同的实例。
这里是我的代码,不起作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __new__(cls, value): if (value =="src"): return Types.Source # elif (value =="nl"): return Types.NetList # else: raise Exception() def __str__(self): if (self == Types.Unknown): return"??" elif (self == Types.Source): return"src" elif (self == Types.NetList): return"nl" |
当我执行python脚本时,会收到以下消息:
1 2 3 4 5 | [...] class Types(enum.Enum): File"C:\Program Files\Python\Python 3.4.0\lib\enum.py", line 154, in __new__ enum_member._value_ = member_type(*args) TypeError: object() takes no parameters |
号
如何返回枚举值的正确实例?
编辑1:此枚举用于URI分析,特别是用于分析架构。所以我的URI看起来像这样
1 2 | nl:PoC.common.config <schema>:<namespace>[.<subnamespace>*].entity |
因此,在一个简单的string.split操作之后,我将把URI的第一部分传递给枚举创建。
1 | type = Types(splitList[0]) |
。
类型现在应包含具有3个可能值(未知、源、netlist)的枚举类型的值。
如果我允许在枚举的成员列表中使用别名,就不可能重复枚举的值alias free。
您的
在这种情况下,凌驾于
1 2 3 4 5 6 7 8 | class Types(enum.Enum): Unknown = 0 Source = 1 src = 1 NetList = 2 nl = 2 |
这里,
您认为不可能迭代枚举的值alias free的断言是错误的。迭代显式不包括别名:
Iterating over the members of an enum does not provide the aliases
号
别名是
演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | >>> import enum >>> class Types(enum.Enum): ... Unknown = 0 ... Source = 1 ... src = 1 ... NetList = 2 ... nl = 2 ... def __str__(self): ... if self is Types.Unknown: return '??' ... if self is Types.Source: return 'src' ... if self is Types.Netlist: return 'nl' ... >>> list(Types) [<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>] >>> list(Types.__members__) ['Unknown', 'Source', 'src', 'NetList', 'nl'] >>> Types.Source <Types.Source: 1> >>> str(Types.Source) 'src' >>> Types.src <Types.Source: 1> >>> str(Types.src) 'src' >>> Types['src'] <Types.Source: 1> >>> Types.Source is Types.src True |
号
这里唯一缺少的是将未知模式转换为
1 2 3 4 | try: scheme = Types[scheme] except KeyError: scheme = Types.Unknown |
超越
如果要将字符串视为值,并使用调用而不是项访问,则这是重写元类的
1 2 3 4 5 6 7 8 9 10 11 | class TypesEnumMeta(enum.EnumMeta): def __call__(cls, value, *args, **kw): if isinstance(value, str): # map strings to enum values, defaults to Unknown value = {'nl': 2, 'src': 1}.get(value, 0) return super().__call__(value, *args, **kw) class Types(enum.Enum, metaclass=TypesEnumMeta): Unknown = 0 Source = 1 NetList = 2 |
。
演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> class TypesEnumMeta(enum.EnumMeta): ... def __call__(cls, value, *args, **kw): ... if isinstance(value, str): ... value = {'nl': 2, 'src': 1}.get(value, 0) ... return super().__call__(value, *args, **kw) ... >>> class Types(enum.Enum, metaclass=TypesEnumMeta): ... Unknown = 0 ... Source = 1 ... NetList = 2 ... >>> Types('nl') <Types.NetList: 2> >>> Types('?????') <Types.Unknown: 0> |
请注意,我们在这里将字符串值转换为整数,并将其余部分保留为原始枚举逻辑。
完全支持值别名因此,
您还必须提供
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class ValueAliasEnumDict(enum._EnumDict): def __init__(self): super().__init__() self._value_aliases = {} def __setitem__(self, key, value): if key in self: # register a value alias self._value_aliases[value] = self[key] else: super().__setitem__(key, value) class ValueAliasEnumMeta(enum.EnumMeta): @classmethod def __prepare__(metacls, cls, bases): return ValueAliasEnumDict() def __new__(metacls, cls, bases, classdict): enum_class = super().__new__(metacls, cls, bases, classdict) enum_class._value_aliases_ = classdict._value_aliases return enum_class def __call__(cls, value, *args, **kw): if value not in cls. _value2member_map_: value = cls._value_aliases_.get(value, next(iter(Types)).value) return super().__call__(value, *args, **kw) |
。
然后,可以在枚举类中定义别名和默认值:
1 2 3 4 5 6 7 8 | class Types(enum.Enum, metaclass=ValueAliasEnumMeta): Unknown = 0 Source = 1 Source = 'src' NetList = 2 NetList = 'nl' |
。
演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> class Types(enum.Enum, metaclass=ValueAliasEnumMeta): ... Unknown = 0 ... Source = 1 ... Source = 'src' ... NetList = 2 ... NetList = 'nl' ... >>> Types.Source <Types.Source: 1> >>> Types('src') <Types.Source: 1> >>> Types('?????') <Types.Unknown: 0> |
是的,如果小心的话,可以重写
我的意思是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __str__(self): if (self == Types.Unknown): return"??" elif (self == Types.Source): return"src" elif (self == Types.NetList): return"nl" else: raise TypeError(self) def _Types_parser(cls, value): if not isinstance(value, str): # forward call to Types' superclass (enum.Enum) return super(Types, cls).__new__(cls, value) else: # map strings to enum values, default to Unknown return { 'nl': Types.NetList, 'ntl': Types.NetList, # alias 'src': Types.Source,}.get(value, Types.Unknown) setattr(Types, '__new__', _Types_parser) print("Types('nl') ->", Types('nl')) # Types('nl') -> nl print("Types('ntl') ->", Types('ntl')) # Types('ntl') -> nl print("Types('wtf') ->", Types('wtf')) # Types('wtf') -> ?? print("Types(1) ->", Types(1)) # Types(1) -> src |
号
更新
下面是一个表驱动的版本,它有助于消除一些重复的编码,否则会涉及到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from collections import OrderedDict import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 __str__ = lambda self: Types._value_to_str.get(self) # define after Types class Types.__new__ = lambda cls, value: (cls._str_to_value.get(value, Types.Unknown) if isinstance(value, str) else super(Types, cls).__new__(cls, value)) # define look-up table and its inverse Types._str_to_value = OrderedDict((( '??', Types.Unknown), ('src', Types.Source), ('ntl', Types.NetList), # alias ( 'nl', Types.NetList),)) Types._value_to_str = {val: key for key, val in Types._str_to_value.items()} Types._str_to_value = dict(Types._str_to_value) # convert to regular dict (optional) if __name__ == '__main__': print("Types('nl') ->", Types('nl')) # Types('nl') -> nl print("Types('ntl') ->", Types('ntl')) # Types('ntl') -> nl print("Types('wtf') ->", Types('wtf')) # Types('wtf') -> ?? print("Types(1) ->", Types(1)) # Types(1) -> src print() print(list(Types)) # iterate values import pickle # demostrate picklability print(pickle.loads(pickle.dumps(Types.NetList)) == Types.NetList) # -> True |
号
我没有足够的rep来评论接受的答案,但是在使用Enum34包的python 2.7中,在运行时会发生以下错误:
"必须使用实例myEnum作为第一个参数调用未绑定方法
我可以通过改变:
1 2 3 4 | # define after Types class Types.__new__ = lambda cls, value: (cls._str_to_value.get(value, Types.Unknown) if isinstance(value, str) else super(Types, cls).__new__(cls, value)) |
。
对于以下内容,使用staticMethod()包装lambda:
1 2 3 4 5 | # define after Types class Types.__new__ = staticmethod( lambda cls, value: (cls._str_to_value.get(value, Types.Unknown) if isinstance(value, str) else super(Types, cls).__new__(cls, value))) |
这段代码在python 2.7和3.6中都进行了正确的测试。
我认为解决您的问题最简单的方法是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | from enum import Enum Types = Enum( value='Types', names=[ ('??', 0), ('Unknown', 0), ('src', 1), ('Source', 1), ('nl', 2), ('NetList', 2), ] ) |
这将创建具有名称别名的枚举。注意
1 2 3 4 | >>> Types.src <Types.src: 1> >>> Types.Source <Types.src: 1> |
号
要使用
1 2 3 4 5 6 7 8 | >>> Types.__str__ = lambda self: self.name >>> Types.__format__ = lambda self, _: self.name >>> str(Types.Unknown) '??' >>> '{}'.format(Types.Source) 'src' >>> Types['src'] <Types.src: 1> |
注意,我们还替换了由
Is it possible to override
__new__ in a python enum to parse strings to an instance?
号
总之,是的。正如Martineau所说明的,在类被声明之后,可以替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __str__(self): if (self == Types.Unknown): return"??" elif (self == Types.Source): return"src" elif (self == Types.NetList): return"nl" else: raise TypeError(self) # completely unnecessary def _Types_parser(cls, value): if not isinstance(value, str): raise TypeError(value) else: # map strings to enum values, default to Unknown return { 'nl': Types.NetList, 'ntl': Types.NetList, # alias 'src': Types.Source,}.get(value, Types.Unknown) setattr(Types, '__new__', _Types_parser) |
。
而且正如他的演示代码所示,如果您不十分小心,您将打破其他事情,如酸洗,甚至基本成员逐值查找:
1 2 3 4 5 6 7 8 9 | --> print("Types(1) ->", Types(1)) # doesn't work Traceback (most recent call last): ... TypeError: 1 --> import pickle --> pickle.loads(pickle.dumps(Types.NetList)) Traceback (most recent call last): ... TypeError: 2 |
Martijn展示了一种提高
1 2 3 4 5 6 7 8 9 | class TypesEnumMeta(enum.EnumMeta): def __call__(cls, value, *args, **kw): if isinstance(value, str): # map strings to enum values, defaults to Unknown value = {'nl': 2, 'src': 1}.get(value, 0) return super().__call__(value, *args, **kw) class Types(enum.Enum, metaclass=TypesEnumMeta): ... |
。
但这使我们有了重复的代码,并针对枚举类型进行工作。
对用例的基本枚举支持中唯一缺少的是拥有一个成员的能力是默认的,但是即使在一个普通的
您需要的类是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Types(enum.Enum): Unknown = 0 Source = 1 src = 1 NetList = 2 nl = 2 def __str__(self): if self is Types.Unknown: return"??" elif self is Types.Source: return"src" elif self is Types.NetList: return"nl" @classmethod def get(cls, name): try: return cls[name] except KeyError: return cls.Unknown |
号
在行动中:
1 2 3 4 5 6 7 8 9 | --> for obj in Types: ... print(obj) ... ?? src nl --> Types.get('PoC') <Types.Unknown: 0> |
号
如果您确实需要值别名,甚至可以在不使用元类黑客的情况下处理它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Types(Enum): Unknown = 0, Source = 1, 'src' NetList = 2, 'nl' def __new__(cls, int_value, *value_aliases): obj = object.__new__(cls) obj._value_ = int_value for alias in value_aliases: cls._value2member_map_[alias] = obj return obj print(list(Types)) print(Types(1)) print(Types('src')) |
号
这给了我们:
1 2 3 | [<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>] Types.Source Types.Source |
号