Python 是否可以在枚举中重写_new _uu以将字符串解析为实例?

Python 是否可以在枚举中重写_new _uu以将字符串解析为实例?,python,enums,python-3.4,Python,Enums,Python 3.4,我想将字符串解析为python枚举。通常会实现一个解析方法来实现这一点。几天前,我发现了\uuuu new\uuu方法,它能够根据给定的参数返回不同的实例 这里是我的代码,它不起作用: import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __new__(cls, value): if (value == "src"): return Types.Source # e

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

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

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

[...]
  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看起来像这样

nl:PoC.common.config
<schema>:<namespace>[.<subnamespace>*].entity
类型现在应该包含一个枚举类型的值,其中包含3个可能的值(未知、源、网络列表)


如果我允许在枚举的成员列表中使用别名,那么就不可能无别名地迭代枚举的值。

您的
枚举上的
\uuuu new\uuuu
方法。enum
类型用于创建枚举值的新实例,因此
类型.Unknown
类型.Source
等都是单例实例。枚举调用(例如,
Types('nl')
EnumMeta.\uuuu call\uuuu
处理,您可以将其子类化

使用名称别名适合您的用例 在这种情况下,重写
\uuuuuu调用\uuuuuuu
可能有些过分。相反,您可以轻松使用:

这里的
Types.nl
是一个别名,将返回与
Types.Netlist
相同的对象。然后(使用
Types[…]
索引访问);因此
Types['nl']
工作并返回
Types.Netlist

您关于无法迭代无别名枚举值的断言是不正确的。迭代:

迭代枚举的成员不会提供别名

别名是
枚举的一部分。如果您仍然需要访问别名,则别名是有序字典的一部分

演示:

>>> 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
覆盖调用 如果要将字符串视为值,并使用调用而不是项访问,则可以使用以下方法覆盖元类的
\uuuuu call\uuuu
方法:

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
演示:

然后,您可以在enum类中定义别名和默认值:

class Types(enum.Enum, metaclass=ValueAliasEnumMeta):
    Unknown = 0

    Source = 1
    Source = 'src'

    NetList = 2
    NetList = 'nl'
演示:

类类型(enum.enum,metaclass=ValueAliasEnumMeta): …未知=0 …来源=1 …源='src' …网络列表=2 …网络列表='nl' ... >>>类型.来源 >>>类型('src') >>>类型(“???”)
是否可以在python枚举中重写
\uuuu new\uuu
,以将字符串解析为实例

一句话,是的。正如martineau所说明的,在实例化类(他的原始代码)后,您可以替换
\uuuu new\uuuu
方法:

同时,正如他的演示代码所示,如果您不十分小心,您将破坏其他东西,例如酸洗,甚至是基本的按值成员查找:

--> 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展示了一种增强
EnumMeta
以获得我们想要的东西的聪明方法:

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):
    ...
但这会使我们有重复的代码,并针对枚举类型进行工作

您的用例缺少基本枚举支持的唯一一点是能够将一个成员作为默认成员,但即使是这样,也可以通过创建新的类方法在正常的
Enum
子类中优雅地处理

您想要的类是:

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
在行动中:

--> for obj in Types:
...   print(obj)
... 
??
src
nl

--> Types.get('PoC')
<Types.Unknown: 0>
这给了我们:

[<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>]
Types.Source
Types.Source
[,]
类型.来源
类型.来源

是的,如果小心的话,可以重写
enum
子类的
\uuu new\uuu()
方法来实现解析方法,但是为了避免在两个位置指定整数编码,需要在类之后单独定义该方法,以便可以引用枚举定义的符号名

我的意思是:

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
更新 下面是一个更为表驱动的版本,它消除了一些可能涉及的重复编码:

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

我认为解决您的问题最简单的方法是使用
Enum
类的函数API,它在选择名称时提供了更多的自由,因为我们将它们指定为字符串:

from enum import Enum

Types = Enum(
    value='Types',
    names=[
        ('??', 0),
        ('Unknown', 0),
        ('src', 1),
        ('Source', 1),
        ('nl', 2),
        ('NetList', 2),
    ]
)
这将创建一个带有名称别名的枚举。请注意
名称
列表中条目的顺序。第一个条目将被选为默认值(也会为
名称
返回),其他条目将被视为别名,但两者都可以使用:

>>> Types.src
<Types.src: 1>
>>> Types.Source
<Types.src: 1>

请注意,我们还替换了由
str.format()
调用的
\uuuuuuuuuuuuuuuuuuuuuuuu
方法,我没有足够的代表对接受的答案发表评论,但在使用enum34包的Python 2.7中,在运行时会发生以下错误:

“必须使用实例MyEnum作为第一个参数调用未绑定的方法()(改为使用EnumMeta实例)”

我可以通过更改以下内容来纠正此问题:

# 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包装到中:


这段代码在Python 2.7和3.6中都得到了正确的测试。

肯定是可能的。有一些很好的例子。@JohnC:除了
\uuuu new\uuu
方法不用于
类型(0)
类型('nl')
。它用于创建
类型。Source
类型。未知的
值对象。为了澄清一些问题:a)枚举成员应该是唯一的(为了简化代码,我在类定义前面删除了@enum.unique.b),因此不能将所有可能的匹配值添加为枚举成员(如NetList、nl、list等)。c) 是的,编写一个parse(value)方法可以很好地工作(例如.NET使用这个模式),但是我用python的方式锁定了一个更好的解决方案:)你不想要别名,但你正在添加别名?这是没有意义的。允许解析值中的别名,但不允许枚举本身中的别名。正如下面提到的一些注释:要解析的值可以是一个字符串,如果是这样,就没有名称的1对1映射。使用和扩展
EnumMeta
@MartijnPieters的有趣示例感谢您的两个解决方案。解决方案1:第一个不会
--> for obj in Types:
...   print(obj)
... 
??
src
nl

--> Types.get('PoC')
<Types.Unknown: 0>
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'))
[<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>]
Types.Source
Types.Source
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
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
# 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()}
from enum import Enum

Types = Enum(
    value='Types',
    names=[
        ('??', 0),
        ('Unknown', 0),
        ('src', 1),
        ('Source', 1),
        ('nl', 2),
        ('NetList', 2),
    ]
)
>>> Types.src
<Types.src: 1>
>>> Types.Source
<Types.src: 1>
>>> Types.__str__ = lambda self: self.name
>>> Types.__format__ = lambda self, _: self.name
>>> str(Types.Unknown)
'??'
>>> '{}'.format(Types.Source)
'src'
>>> Types['src']
<Types.src: 1>
# 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 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)))