web-dev-qa-db-ja.com

文字列表現を整数値を使用するEnumに関連付けますか?

私は整数値を持つ列挙型を作成しようとしていますが、各値に対して表示に適した文字列を返すこともできます。文字列に値をマッピングするdictを定義してから、__str__と文字列引数を持つ静的コンストラクタを実装できると思っていましたが、それに問題があります...

(異なる状況下では、このEnumの基になるデータ型を整数ではなく文字列にすることができましたが、これはenumデータベーステーブルのマッピングとして使用されているため、整数値と文字列の両方が意味があります。前者主キーである。)

from enum import Enum

class Fingers(Enum):
    THUMB = 1
    INDEX = 2
    MIDDLE = 3
    RING = 4
    PINKY = 5

    _display_strings = {
        THUMB: "thumb",
        INDEX: "index",
        MIDDLE: "middle",
        RING: "ring",
        PINKY: "pinky"
        }

    def __str__(self):
        return self._display_strings[self.value]

    @classmethod
    def from_string(cls, str1):
        for val, str2 in cls._display_strings.items():
            if str1 == str2:
                return cls(val)
        raise ValueError(cls.__name__ + ' has no value matching "' + str1 + '"')

文字列に変換すると、次のエラーが発生します。

>>> str(Fingers.RING)
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    str(Fingers.RING)
  File "D:/src/Hacks/PythonEnums/fingers1.py", line 19, in __str__
    return self._display_strings[self.value]
TypeError: 'Fingers' object is not subscriptable

問題は、Enumがすべてのクラス変数をenum値として使用することで、基になる型ではなくEnum型のオブジェクトを返すように見えることです。

考えられるいくつかの回避策は次のとおりです。

  1. 辞書をFingers._display_strings.valueとして参照します。 (ただし、Fingers.__display_stringsは有効な列挙値になります!)
  2. Dictをクラス変数ではなくモジュール変数にする。
  3. __str__関数とfrom_string関数で、dictを複製します(おそらく、一連のifステートメントに分解します)。
  4. Dictをクラス変数にするのではなく、静的メソッド_get_display_stringsを定義してdictを返すようにします。これにより、列挙型の値にはなりません。

上記の初期コードと回避策1.は、基になる整数値をdictキーとして使用することに注意してください。他のすべてのオプションでは、dict(またはifテスト)がクラス自体の直接以外の場所で定義されている必要があるため、これらの値をクラス名で修飾する必要があります。したがって、たとえば、Fingers.THUMBを使用してenumオブジェクトを取得するか、Fingers.THUMB.valueを使用して基礎となる整数値を取得するだけで、THUMBを使用することはできません。基になる整数値をdictキーとして使用する場合は、その値を使用してdictを検索し、[Fingers.THUMB.value]だけでなく、たとえば[Fingers.THUMB]でインデックス付けする必要があります。

したがって、問題は、基になる整数値を保持しながら、Enumの文字列マッピングを実装するための最良または最もPython的な方法は何ですか?

10
David Scarlett

これはstdlib Enumで実行できますが、 aenum ではるかに簡単です1

from aenum import Enum

class Fingers(Enum):

    _init_ = 'value string'

    THUMB = 1, 'two thumbs'
    INDEX = 2, 'offset location'
    MIDDLE = 3, 'average is not median'
    RING = 4, 'round or finger'
    PINKY = 5, 'wee wee wee'

    def __str__(self):
        return self.string

文字列値を介して検索できるようにしたい場合は、新しいクラスメソッド_missing_value_を実装します(stdlibで_missing_のみ):

from aenum import Enum

class Fingers(Enum):

    _init_ = 'value string'

    THUMB = 1, 'two thumbs'
    INDEX = 2, 'offset location'
    MIDDLE = 3, 'average is not median'
    RING = 4, 'round or finger'
    PINKY = 5, 'wee wee wee'

    def __str__(self):
        return self.string

    @classmethod
    def _missing_value_(cls, value):
        for member in cls:
            if member.string == value:
                return member

1 開示:私は Python stdlib Enumenum34 backport 、および Advanced Enumeration(aenum ライブラリ。

9
Ethan Furman

多分私はここでポイントを逃していますが、あなたが定義した場合

class Fingers(Enum):
    THUMB = 1
    INDEX = 2
    MIDDLE = 3
    RING = 4
    PINKY = 5

次にPython 3.6で行うことができます

print (Fingers.THUMB.name.lower())

あなたが望んでいるものだと思います。

3
BoarGules

私が思いついた別の解決策は、整数と文字列の両方が意味があるため、次のようにEnum値を_(int, str)_タプルにすることです。

_from enum import Enum

class Fingers(Enum):
    THUMB = (1, 'thumb')
    INDEX = (2, 'index')
    MIDDLE = (3, 'middle')
    RING = (4, 'ring')
    PINKY = (5, 'pinky')

    def __str__(self):
        return self.value[1]

    @classmethod
    def from_string(cls, s):
        for finger in cls:
            if finger.value[1] == s:
                return finger
        raise ValueError(cls.__+ ' has no value matching "' + s + '"')
_

ただし、これは、FingersオブジェクトのreprがintだけではなくTupleを表示することを意味します。Fingersオブジェクトを作成するには、intだけでなく、完全なTupleを使用する必要があります。つまりf = Fingers((1, 'thumb'))はできますが、f = Fingers(1)はできません。

_>>> Fingers.THUMB
<Fingers.THUMB: (1, 'thumb')>
>>> Fingers((1,'thumb'))
<Fingers.THUMB: (1, 'thumb')>
>>> Fingers(1)
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    Fingers(1)
  File "C:\Python\Python35\lib\enum.py", line 241, in __call__
    return cls.__new__(cls, value)
  File "C:\Python\Python35\lib\enum.py", line 476, in __new__
    raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 1 is not a valid Fingers
_

そのためのさらに複雑な回避策は、Enumのメタクラスをサブクラス化してカスタム___call___を実装することです。 (少なくとも___repr___のオーバーライドははるかに簡単です!)

_from enum import Enum, EnumMeta

class IntStrTupleEnumMeta(EnumMeta):
    def __call__(cls, value, names=None, *args, **kwargs):
        if names is None and isinstance(value, int):
            for e in cls:
                if e.value[0] == value:
                    return e

        return super().__call__(value, names, **kwargs)

class IntStrTupleEnum(Enum, metaclass=IntStrTupleEnumMeta):
    pass

class Fingers(IntStrTupleEnum):
    THUMB = (1, 'thumb')
    INDEX = (2, 'index')
    MIDDLE = (3, 'middle')
    RING = (4, 'ring')
    PINKY = (5, 'pinky')

    def __str__(self):
        return self.value[1]

    @classmethod
    def from_string(cls, s):
        for finger in cls:
            if finger.value[1] == s:
                return finger
        raise ValueError(cls.__+ ' has no value matching "' + s + '"')

    def __repr__(self):
        return '<%s.%s %s>' % (self.__class__.__name__, self.name, self.value[0])
_

この実装と通常のint Enumの違いの1つは、整数値は同じだが文字列が異なる値(たとえば、INDEX = (2, 'index')POINTER = (2, 'pointer'))は、同じFingerオブジェクト、プレーンなEnumの場合、_Finger.POINTER is Finger.INDEX_はTrueと評価されます。

1
David Scarlett

これはOPが要求したものではありませんが、値がintかどうかを気にしない場合の1つの優れたオプションです。人間が読める文字列として値を使用できます。

ソース: https://docs.python.org/3/library/enum.html

値の省略多くのユースケースでは、列挙の実際の値が何であるかを気にしません。このタイプの単純な列挙を定義するには、いくつかの方法があります。

値としてautoのインスタンスを使用する値としてオブジェクトのインスタンスを使用する値として説明文字列を使用する値としてタプルを使用するカスタムnew( )タプルをint値で置き換えるこれらのメソッドのいずれかを使用すると、これらの値が重要ではないことをユーザーに示し、残りのメンバーに番号を付け直さなくてもメンバーを追加、削除、または並べ替えることができます。

どちらの方法を選択する場合でも、(重要でない)値を非表示にするrepr()を提供する必要があります。

class NoValue(Enum):
     def __repr__(self):
         return '<%s.%s>' % (self.__class__.__name__, self.name)

説明的な文字列の使用値として文字列を使用すると、次のようになります。

class Color(NoValue):
     RED = 'stop'
     GREEN = 'go'
     BLUE = 'too fast!'

Color.BLUE
<Color.BLUE>
Color.BLUE.value
'too fast!'
1
Neeraj Gulia

GUIコンボボックス(PyGTK内)の表示文字列が必要なのと同じ問題がありました。私はソリューションのPythonicity(それは一言ですか?)について知りませんが、これを使用しました:

from enum import IntEnum
class Finger(IntEnum):
    THUMB = 1
    INDEX = 2
    MIDDLE = 3
    RING = 4
    PINKY = 5

    @classmethod
    def names(cls):
        return ["Thumb", "Index", "Middle", "Ring", "Pinky"]

    @classmethod
    def tostr(cls, value):
        return cls.names()[value - cls.THUMB]

    @classmethod
    def toint(cls, s):
        return cls.names().index(s) + cls.THUMB

あなたのコードからそれらを使用する:

>>> print(Finger.INDEX)
Finger.INDEX
>>> print(Finger.tostr(Finger.THUMB))
Thumb
>>> print(Finger.toint("Ring"))
4
0
mmeisner

良い質問しかし、これはFingersオブジェクトのreprがintだけでなくTupleを表示することを意味し、intだけでなく、完全なTupleを使用してFingersオブジェクトを作成する必要があります。つまりできるよ

f = Fingers((1, 'thumb')) 

だがしかし

f = Fingers(1)
0
Anil Pathariya

列挙型が整数でなければならないのはなぜですか?表示する文字列を含め、何でもかまいません。

THUMB = 'Thumb'
INDEX = 'Index'
MIDDLE = 'Middle'
RING = 'Ring'
PINKY =  'Pinky'

タイプに関係なく、比較などを行うことができます。それらについて算術演算を行わないでください。

0
MikeyB