web-dev-qa-db-ja.com

enumメンバーの値をその名前に自動的に設定する

私はpythonのenumライブラリをいじり回していて、難問に遭遇しました。ドキュメントでは、何かが定義されている auto-numbering enum の例を示しています。

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

同様のクラスを作成したいのですが、値はメンバーの名前から自動的に設定され、strenumを実行することで得られる機能を維持します mixin stuff

だから次のようなもの:

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

Animal.dog == 'dog' # True

Enumモジュールのソースコードを見て、__new__EnumMetaクラスをいじくり回して多くのバリエーションを試しました

19

更新:2017-03-01

Python 3.6(および _Aenum 2.0_1Flag および IntFlag クラスが追加されました。その一部は新しい auto() helper でしたので、これは非常に簡単になります。

_>>> 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クラスの問題は、列挙型メンバーの名前がそれを作成するコードに渡されないため、使用できないことです。もう1つのしわがあるのは、strが不変であるため、作成された列挙型を(たとえば クラスデコレータ を使用して)変更することはできません。

最も簡単な方法は 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メンバーはではないことに注意してくださいstrs。


あなたがそれを難し​​い方法でやりたい場合:EnumMetaをサブクラス化し、新しいEnumのクラス辞書beforeをいじるメンバーが作成されます:

_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_ backport 、および Advanced Enumeration(aenum ライブラリ。

19
Ethan Furman

おそらくあなたは 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)
1
Dunes