web-dev-qa-db-ja.com

拡張する方法Python Enum?

EnumタイプをPython 3.4で拡張するためのベストプラクティスは何ですか?また、これを行う可能性はありますか?

例えば:

from enum import Enum

class EventStatus(Enum):
   success = 0
   failure = 1

class BookingStatus(EventStatus):
   duplicate = 2
   unknown = 3

Traceback (most recent call last):
...
TypeError: Cannot extend enumerations

現在、メンバーを持つ基本列挙クラスを作成し、他の列挙クラスで使用する方法はありません(上記の例のように)。 Python列挙型の継承を実装する他の方法はありますか?

33
falek.marcin

列挙がメンバーを定義しない場合にのみ、列挙のサブクラス化が許可されます。

メンバーを定義する列挙型のサブクラス化を許可すると、型とインスタンスのいくつかの重要な不変条件に違反することになります。

https://docs.python.org/3/library/enum.html#restricted-subclassing-of-enumerations

したがって、no、直接は不可能です。

21
GingerPlusPlus

Enumクラスを直接呼び出してチェーンを使用すると、既存の列挙型の拡張(結合)が可能になります。

CANopenの実装に取り​​組んでいるときに列挙型を拡張する問題に出くわしました。 0x1000から0x2000の範囲のパラメーターインデックスは、すべてのCANopenノードに共通です。 0x6000以降の範囲は、ノードがドライブ、ioモジュールなどであるかどうかに依存します。

nodes.py:

from enum import IntEnum

class IndexGeneric(IntEnum):
    """ This enum holds the index value of genric object entrys
    """
    DeviceType    = 0x1000
    ErrorRegister = 0x1001

Idx = IndexGeneric

drives.py:

from itertools import chain
from enum import IntEnum
from nodes import IndexGeneric

class IndexDrives(IntEnum):
    """ This enum holds the index value of drive object entrys
    """
    ControlWord   = 0x6040
    StatusWord    = 0x6041
    OperationMode = 0x6060

Idx= IntEnum('Idx', [(i.name, i.value) for i in chain(IndexGeneric,IndexDrives)])
12
Jul3k

一般的ではありませんが、多くのモジュールから列挙型を作成すると便利な場合があります。 aenum1 ライブラリはextend_enum関数でこれをサポートします:

from aenum import Enum, extend_enum

class Index(Enum):
    DeviceType    = 0x1000
    ErrorRegister = 0x1001

for name, value in (
        ('ControlWord', 0x6040),
        ('StatusWord', 0x6041),
        ('OperationMode', 0x6060),
        ):
    extend_enum(Index, name, value)

assert len(Index) == 5
assert list(Index) == [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode]
assert Index.DeviceType.value == 0x1000
assert Index.StatusWord.value == 0x6041

1 開示:私は Python stdlib Enumenum34バックポート 、および 高度な列挙(aenum ライブラリ。

9
Ethan Furman

この問題に対してメタクラスアプローチを使用することを選択しました。

_from enum import EnumMeta

class MetaClsEnumJoin(EnumMeta):
    """
    Metaclass that creates a new `enum.Enum` from multiple existing Enums.

    @code
        from enum import Enum

        ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
        ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
        class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
            pass

        print(ENUMJOINED.a)
        print(ENUMJOINED.b)
        print(ENUMJOINED.c)
        print(ENUMJOINED.d)
    @endcode
    """

    @classmethod
    def __prepare__(metacls, name, bases, enums=None, **kargs):
        """
        Generates the class's namespace.
        @param enums Iterable of `enum.Enum` classes to include in the new class.  Conflicts will
            be resolved by overriding existing values defined by Enums earlier in the iterable with
            values defined by Enums later in the iterable.
        """
        #kargs = {"myArg1": 1, "myArg2": 2}
        if enums is None:
            raise ValueError('Class keyword argument `enums` must be defined to use this metaclass.')
        ret = super().__prepare__(name, bases, **kargs)
        for enm in enums:
            for item in enm:
                ret[item.name] = item.value  #Throws `TypeError` if conflict.
        return ret

    def __new__(metacls, name, bases, namespace, **kargs):
        return super().__new__(metacls, name, bases, namespace)
        #DO NOT send "**kargs" to "type.__new__".  It won't catch them and
        #you'll get a "TypeError: type() takes 1 or 3 arguments" exception.

    def __init__(cls, name, bases, namespace, **kargs):
        super().__init__(name, bases, namespace)
        #DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older.  You'll get a
        #"TypeError: type.__init__() takes no keyword arguments" exception.
_

このメタクラスは次のように使用できます。

_>>> from enum import Enum
>>>
>>> ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
>>> ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
>>> class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
...     e = 5
...     f = 6
...
>>> print(repr(ENUMJOINED.a))
<ENUMJOINED.a: 1>
>>> print(repr(ENUMJOINED.b))
<ENUMJOINED.b: 2>
>>> print(repr(ENUMJOINED.c))
<ENUMJOINED.c: 3>
>>> print(repr(ENUMJOINED.d))
<ENUMJOINED.d: 4>
>>> print(repr(ENUMJOINED.e))
<ENUMJOINED.e: 5>
>>> print(repr(ENUMJOINED.f))
<ENUMJOINED.f: 6>
_

名前空間の競合が発生した場合に何が起こるかに注意してください:

_>>> ENUMC = Enum('ENUMA', {'a': 1, 'b': 2})
>>> ENUMD = Enum('ENUMB', {'a': 3})
>>> class ENUMJOINEDCONFLICT(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMC, ENUMD)):
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 19, in __prepare__
  File "C:\Users\jcrwfrd\AppData\Local\Programs\Python\Python37\lib\enum.py", line 100, in __setitem__
    raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'a'
>>>
_

これは、基本的な_enum.EnumMeta.__prepare___が、キーの割り当て時に異なる動作をする典型的なdictオブジェクトではなく、特別な_enum._EnumDict_を返すためです。このエラーメッセージをtry-_except TypeError_で囲むことで抑制するか、super().__prepare__(...)を呼び出す前にネームスペースを変更する方法があります。

このアプローチは、ソースEnumsと同じ名前と値のペアを使用して新しいEnumを作成しますが、結果のEnumメンバーは一意です。名前と値は同じですが、特定の比較に失敗します。

_>>> ENUMA.b.name == ENUMJOINED.b.name
True
>>> ENUMA.b.value == ENUMJOINED.b.value
True
>>> ENUMA.b == ENUMJOINED.b
False
>>> ENUMA.b is ENUMJOINED.b
False
>>>
_
1
John Crawford

私はあなたがこの方法でそれを行うことができると思います:

import enum
from typing import List
from enum import Enum

def extend_enum(current_enum, names: List[str], values: List = None):
    if not values:
        values = names

    for item in current_enum:
        names.append(item.name)
        values.append(item.value)

    return enum.Enum(current_enum.__name__, dict(Zip(names, values)))

class EventStatus(Enum):
   success = 0
   failure = 1

class BookingStatus(object):
   duplicate = 2
   unknown = 3

BookingStatus = extend_enum(EventStatus, ['duplicate','unknown'],[2,3])

キーポイントは次のとおりです。

  • pythonは実行時に何でも変えることができます
  • クラスもオブジェクトです
0
foolcage

別の方法 :

Letter = Enum(value="Letter", names={"A": 0, "B": 1})
LetterExtended = Enum(value="Letter", names=dict({"C": 2, "D": 3}, **{i.name: i.value for i in Letter}))

または:

LetterDict = {"A": 0, "B": 1}
Letter = Enum(value="Letter", names=LetterDict)

LetterExtendedDict = dict({"C": 2, "D": 3}, **LetterDict)
LetterExtended = Enum(value="Letter", names=LetterExtendedDict)

出力:

>>> Letter.A
<Letter.A: 0>
>>> Letter.C
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "D:\jhpx\AppData\Local\Programs\Python\Python36\lib\enum.py", line 324, in __getattr__
    raise AttributeError(name) from None
AttributeError: C
>>> LetterExtended.A
<Letter.A: 0>
>>> LetterExtended.C
<Letter.C: 2>
0
jhpx