是否可以覆盖枚举中的 __new__ 以将字符串解析为实例? [英] Is it possible to override __new__ in an enum to parse strings to an instance?

查看:17
本文介绍了是否可以覆盖枚举中的 __new__ 以将字符串解析为实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将字符串解析为 python 枚举.通常人们会实现一个解析方法来做到这一点.几天前,我发现了 __new__ 方法,它能够根据给定的参数返回不同的实例.

I want to parse strings into python enums. Normally one would implement a parse method to do so. A few days ago I spotted the __new__ method which is capable of returning different instances based on a given parameter.

这是我的代码,它不起作用:

Here my code, which will not work:

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 脚本时,我收到以下消息:

When I execute my Python script, I get this message:

[...]
  class Types(enum.Enum):
File "C:Program FilesPythonPython 3.4.0libenum.py", line 154, in __new__
  enum_member._value_ = member_type(*args)
TypeError: object() takes no parameters

如何返回枚举值的正确实例?

How can I return a proper instance of a enum value?

这个 Enum 用于 URI 解析,特别是用于解析模式.所以我的 URI 看起来像这样

This Enum is used in URI parsing, in particular for parsing the schema. So my URI would look like this

nl:PoC.common.config
<schema>:<namespace>[.<subnamespace>*].entity

所以在一个简单的 string.split 操作之后,我会将 URI 的第一部分传递给枚举创建.

So after a simple string.split operation I would pass the first part of the URI to the enum creation.

type = Types(splitList[0])

type 现在应该包含一个枚举类型的值,有 3 个可能的值(未知、来源、网络列表)

type should now contain a value of the enum Types with 3 possible values (Unknown, Source, NetList)

如果我允许在枚举的成员列表中使用别名,就不可能自由迭代枚举的值.

If I would allow aliases in the enum's member list, it won't be possible to iterate the enum's values alias free.

推荐答案

是的,您可以覆盖 enum 子类的 __new__() 方法来实现解析方法如果你小心,但为了避免在两个地方指定整数编码,你需要分别定义方法,类之后,这样你就可以引用由枚举.

Yes, you can override the __new__() method of an enum subclass to implement a parse method if you're careful, but in order to avoid specifying the integer encoding in two places, you'll need to define the method separately, after the class, so you can reference the symbolic names defined by the enumeration.

我的意思是:

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)


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

更新

这是一个更由表格驱动的版本,它消除了一些否则会涉及的重复编码:

Update

Here's a more table-driven version that eliminates some of the repetitious coding that would otherwise be involved:

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()}


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(list(Types))  # -> [<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>]

    import pickle  # Demostrate picklability
    print(pickle.loads(pickle.dumps(Types.NetList)) == Types.NetList)  # -> True

注意,在 Python 3.7+ 中,常规字典是有序的,因此不需要在上面的代码中使用 OrderedDict,它可以简化为:

Note that in Python 3.7+ regular dictionaries are ordered, so the use of OrderedDict in the code above would not be needed and it could be simplified to just:

# Define look-up table and its inverse.
Types._str_to_value = {'??': 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()}

这篇关于是否可以覆盖枚举中的 __new__ 以将字符串解析为实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆