2017-05-09 23 views
3

我試圖創建一個枚舉,該整數值具有整型值,但也可以爲每個值返回一個適合顯示的字符串。我在想,我可以只是定義一個字典映射值的字符串,然後執行__str__和靜態構造函數與字符串參數,但有一個問題...將字符串表示與使用整數值的Python 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類型的對象,而不是它們的基礎類型。

我能想到的幾個解決方法包括:

  1. 參考字典爲Fingers._display_strings.value。 (然後Fingers.__display_strings成爲一個有效的枚舉值!)
  2. 使字典變成一個模塊變量而不是類變量。
  3. __str__from_string函數中複製字典(可能也將其拆分爲一系列if語句)。
  4. 而不是使字典變成類變量,定義一個靜態方法_get_display_strings返回字典,所以它不會成爲一個枚舉值。

請注意,上面的初始代碼和變通方法1.使用底層整數值作爲字典鍵。其他選項都要求字典(或if測試)在類中直接定義的地方以外,因此它必須用類名限定這些值。因此,您只能使用Fingers.THUMB來獲取枚舉對象,或者使用Fingers.THUMB.value來獲取底層整數值,而不僅僅是THUMB。如果使用底層整數值作爲字典密鑰,那麼您還必須使用它來查找字典,使用例如[Fingers.THUMB.value]而非[Fingers.THUMB]對其進行索引。

所以,問題是,什麼是最好或最Pythonic的方式來實現一個枚舉的字符串映射,同時保留一個基礎的整數值?

回答

3

這可以用STDLIB Enum來完成,但與aenum 容易得多:

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_(只是_missing_在STDLIB):

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 

披露:我是Python stdlib Enumenum34 backportAdvanced Enumeration (aenum)庫的作者。

+0

感謝您的回答:)。我想知道,你應該如何使用這種缺失值的方法?我的意思是,我可以做'MyEnum._missing_value _(「value」)',但我不確定這是否是使用它的預期方式。使用'MyEnum ['value']'會產生一個異常 – TomDT

+0

@TomDT:當一個未知值被查找時,'Enum'機器會自動調用'_missing_value_'。您需要創建自己的'_missing_value_'方法來提供任何額外的搜索功能。在上面的例子中,OP希望使用'Fingers('thumb')'來獲取'Fingers.THUMB',這通常不會工作,因爲'Fingers.THUMB'是'1'。 –

+0

Oohhh我看到了,我正在使用括號[]來訪問它,但我應該使用parens。謝謝 ! – TomDT

0

我想出的另一個解決方案是,因爲整數和字符串都是有意義的,所以使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.__name__ + ' has no value matching "' + s + '"') 

然而,這意味着一個Fingers對象的再版將顯示的元組,而不僅僅是int和完整的元組必須被用來創建Fingers對象,不只是整型。即您可以執行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__就簡單多了!)這個實現,普通int枚舉之間

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.__name__ + ' has no value matching "' + s + '"') 

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

一個區別是,具有相同的整數值,但不同的字符串值(例如INDEX = (2, 'index')POINTER = (2, 'pointer'))將不會計算爲相同的Finger對象,而使用純int Enum時,Finger.POINTER is Finger.INDEX將評估爲True

0

不錯的問題然而,這意味着Fingers對象的repr將顯示元組而不是int,並且必須使用完整的元組來創建Fingers對象,而不僅僅是int。即你可以做f =手指((1,'thumb')),但不是f =手指(1)。

1

也許我在這裏錯過了點,但如果你定義

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

然後在Python 3.6,你可以做

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

我認爲這是你想要的。

+0

看上去很好,但這只是我在這裏的例子中的一個意外。在我的預期用途中,字符串更長,而不僅僅是枚舉名的小寫版本! –

+0

所以我沒有想到這一點。 – BoarGules