自动将枚举成员的值设置为其名称

问题描述 投票:0回答:3

我一直在摆弄 python 的枚举库,并遇到了一个难题。在文档中,他们展示了一个自动编号枚举的示例,其中定义了一些内容:

class Color(AutoNumber):
    red = ()
    green = ()
    ...

我想创建一个类似的类,但该值会自动根据成员的名称设置,并保留通过执行

str
enum
mixin stuff

获得的功能

所以类似:

class Animal(MagicStrEnum):
    horse = ()
    dog = ()

Animal.dog == 'dog' # True

我查看了枚举模块的源代码,并尝试了很多与

__new__
EnumMeta
类混在一起的变体

python enums metaprogramming metaclass
3个回答
46
投票

更新:2017-03-01

在 Python 3.6(和

Aenum 2.0
1)中,添加了
Flag
IntFlag
类;其中一部分是一个新的
auto()
助手
,它使这变得非常简单:

>>> class AutoName(Enum):
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

原答案

AutoStr
类的困难在于枚举成员的名称没有传递到创建它的代码中,因此它无法使用。 另一个问题是
str
是不可变的,因此我们无法在创建这些类型的枚举后更改它们(例如,通过使用 class 装饰器)。

最简单的方法是使用 Functional API:

Animal = Enum('Animal', [(a, a) for a in ('horse', 'dog')], type=str)

这给了我们:

>>> list(Animal)
[<Animal.horse: 'horse'>, <Animal.dog: 'dog'>]

>>> Animal.dog == 'dog'
True

下一个最简单的事情,假设您想为将来的枚举使用创建一个基类,就像我的

DocEnem

class DocEnum(Enum):
    """
    compares equal to all cased versions of its name
    accepts a doctring for each member
    """
    def __new__(cls, *args):
        """Ignores arguments (will be handled in __init__)"""
        obj = object.__new__(cls)
        obj._value_ = None
        return obj

    def __init__(self, doc=None):
        # first, fix _value_
        self._value_ = self._name_.lower()
        self.__doc__ = doc

    def __eq__(self, other):
        if isinstance(other, basestring):
            return self._value_ == other.lower()
        elif not isinstance(other, self.__class__):
            return NotImplemented
        return self is other

    def __hash__(self):
        # keep DocEnum hashable
        return hash(self._value_)

    def __ne__(self, other):
        return not self == other

使用中:

class SpecKind(DocEnum):
    REQUIRED = "required value"
    OPTION = "single value per name"
    MULTI = "multiple values per name (list form)"
    FLAG = "boolean value per name"
    KEYWORD = 'unknown options'

请注意,与第一个选项不同,

DocEnum
成员是不是
str


如果你想用困难的方式做到这一点:子类

EnumMeta
并在创建成员之前
摆弄新的
Enum的类字典:

from enum import EnumMeta, Enum, _EnumDict class StrEnumMeta(EnumMeta): def __new__(metacls, cls, bases, oldclassdict): """ Scan through `oldclassdict` and convert any value that is a plain tuple into a `str` of the name instead """ newclassdict = _EnumDict() for k, v in oldclassdict.items(): if v == (): v = k newclassdict[k] = v return super().__new__(metacls, cls, bases, newclassdict) class AutoStrEnum(str, Enum, metaclass=StrEnumMeta): "base class for name=value str enums" class Animal(AutoStrEnum): horse = () dog = () whale = () print(Animal.horse) print(Animal.horse == 'horse') print(Animal.horse.name, Animal.horse.value)

这给了我们:

Animal.horse True horse horse


1 披露:我是 Python stdlib Enum

enum34
 向后移植
高级枚举 (aenum
)
库的作者。


1
投票
也许您正在寻找由

name
类自动提供的
Enum
属性

>>> class Animal(Enum): ... ant = 1 ... bee = 2 ... cat = 3 ... dog = 4 ... >>> Animal.ant.name == "ant" True

不过如果你真的想搬起石头砸自己的脚。我确信这会带来一大堆陷阱(我已经消除了最明显的一个)。

from enum import Enum, EnumMeta, _EnumDict class AutoStrEnumDict(_EnumDict): def __setitem__(self, key, value): super().__setitem__(key, key) class AutoStrEnumMeta(EnumMeta): @classmethod def __prepare__(metacls, cls, bases): return AutoStrEnumDict() def __init__(self, name, bases, attrs): super().__init__(name, bases, attrs) # override Enum.__str__ # can't put these on the class directly otherwise EnumMeta overwrites them # should also consider resetting __repr__, __format__ and __reduce_ex__ if self.__str__ is not str.__str__: self.__str__ = str.__str__ class AutoStrNameEnum(str, Enum, metaclass=AutoStrEnumMeta): pass class Animal(AutoStrNameEnum): horse = () dog = () print(Animal.horse) assert Animal.horse == "horse" assert str(Animal.horse) == "horse" # and not equal to "Animal.horse" (the gotcha mentioned earlier)
    

0
投票

自 python3.11 起,您可以将 StrEnumauto()

 一起使用

注意:将 auto 与 StrEnum 一起使用会导致小写的成员名称作为值。

from enum import StrEnum, auto class Animal(StrEnum): HORSE = auto() DOG = auto() assert Animal.DOG == "dog"
    
© www.soinside.com 2019 - 2024. All rights reserved.