2010-12-17 14 views
8

Znalazłem po prostu sposobem realizowania (siekać) enum w Pythonie:klasa Python Enum (z toString fromstring)

class MyEnum: 
    VAL1, VAL2, VAL3 = range(3) 

mogę wtedy zadzwonić to jako takie:

bob = MyEnum.VAL1 

Seksowny!

Dobra, teraz chcę móc uzyskać zarówno wartość liczbową, jeśli podano ciąg znaków, jak i ciąg znaków, jeśli podano wartość liczbową. Powiedzmy, że chcę struny dokładnie dopasować się do klucz Enum na

Najlepszym mogłem pomyśleć jest coś takiego:

class MyEnum: 
    VAL1, VAL2, VAL3 = range(3) 
    @classmethod 
    def tostring(cls, val): 
    if (val == cls.VAL1): 
     return "VAL1" 
    elif (val == cls.VAL2): 
     return "VAL2" 
    elif (val == cls.VAL3): 
     return "VAL3" 
    else: 
     return None 
    @classmethod 
    def fromstring(cls, str): 
    if (str.upper() == "VAL1"): 
     return cls.VAL1 
    elif (str.upper() == "VAL2"): 
     return cls.VAL2 
    elif (str.upper() == "VAL2"): 
     return cls.VAL2 
    else: 
     return None 

czy coś takiego (ignorować jak Łapię nieprawidłowe przypadki)

Czy istnieje lepszy, bardziej skoncentrowany na pythonie sposób robienia tego, co robię powyżej? Czy też powyższe jest już tak zwięzłe, jak to tylko możliwe.

Wygląda na to, że jest lepszy sposób na zrobienie tego.

+4

Ta ilość if/else zawsze oznacza, że ​​jest to złe rozwiązanie;) –

+0

możliwe duplikat [Jaki jest najlepszy sposób, aby wdrożyć 'enum' w Pythonie? ] (http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python) –

+0

@Nick: możesz zmienić zaakceptowaną odpowiedź. –

Odpowiedz

10

Cóż, tutaj jest to, co prosiłeś:

class MyEnum: 
    VAL1, VAL2, VAL3 = range(3) 
    @classmethod 
    def tostring(cls, val): 
    for k,v in vars(cls).iteritems(): 
     if v==val: 
      return k 

    @classmethod 
    def fromstring(cls, str): 
     return getattr(cls, str.upper(), None) 

print MyEnum.fromstring('Val1') 
print MyEnum.tostring(2) 

Ale ja naprawdę nie rozumiem punkt wyliczenia w Pythonie. Ma tak bogaty system typów, jak również generatory i coroutines do zarządzania państwami.

wiem, że nie używałeś wyliczenia w Pythonie dla więcej niż 12 lat, może uda Ci się ich pozbyć zbyt ;-)

+0

MyEnum.tostring (3) zwraca Brak. Prawdopodobnie powinienem być MyEnum.tostring (MyEnum.VAL3) – Rod

+0

To dlatego, że 'zasięg' zaczyna się od 0.' tostring (2) 'daje' VAL3'. Niefortunne nazwy ('* [1-9]'). Afaik, C/C++ 'enum' zaczyna się od 0. – delnan

+0

@delnan True. Byłem zdezorientowany z testu, który zrobiłem po mojej stronie. Prawdopodobnie efekt uboczny, który uniemożliwił mi użycie int bezpośrednio w metodzie tostrowania. – Rod

1

Można użyć słowniki:

class MyEnum: 
    VAL1, VAL2, VAL3 = range(3) 
    __toString = { VAL1 : "VAL1", VAL2 : "VAL2", VAL3 : "VAL3" } 

    @classmethod 
    def tostring(cls, val): 
     return cls.__toString.get(val) 

    @classmethod 
    def fromstring(cls, str): 
     i = str.upper() 
     for k,v in cls.__toString.iteritems(): 
      if v == i: 
       return k 
     return None 


print MyEnum.tostring(MyEnum.VAL1) 
print MyEnum.fromstring("VAL1") 

Edycja: Odpowiedzi THC4k jest zdecydowanie lepiej. Ale zostawiłem moje jako przykład naiwnej implementacji.

+2

Czołowe podwójne podkreślenia (= wymamienie nazwy) są ... no cóż, nie złe, ale nigdy nie widziałem tego hipotetycznego przypadku, w którym mają małą korzyść, w przeciwieństwie do zwykłego przypadku, w którym po prostu łamią rzeczy. – delnan

+0

@delnan. Przyjmę to do wiadomości. – Rod

7

Użyj dict:

MyEnum = {'VAL1': 1, 'VAL2':2, 'VAL3':3} 

Brak zajęcia niezbędnej . Dicts mają klasę pokonaną, ponieważ 1.) są niesamowicie wydajne, 2.) mają zestaw niesamowitych metod wypalanych, a 3.) są uniwersalną konstrukcją językową. Są również rozszerzalne:

MyEnum['VAL4'] = 4 

Nie jest rozsądne wdrażanie funkcji C++ (lub innego języka) w Pythonie. Jeśli odkryjesz, że "hakujesz wyliczankę" lub coś podobnego, możesz założyć farmę, że nie robisz tego w stylu Pythona.

Jeśli chcesz pójść w przeciwną stronę, zbuduj kolejny dykt. (na przykład.{'1':'VAL1', ...}

+2

Jeśli muszę zbudować drugi słownik, aby przejść w drugą stronę, narusza to wartość jednego punktu preferowanego w dowolnym języku. Jak reagujesz na tę obawę? – Stabledog

+1

Rozszerzalność nie jest funkcją wyliczenia. –

3

Patrz: How can I represent an 'Enum' in Python?

Ten jest ciekawy:

class EnumMeta(type): 
    def __getattr__(self, name): 
    return self.values.index(name) 

    def __setattr__(self, name, value): # this makes it read-only 
    raise NotImplementedError 

    def __str__(self): 
    args = {'name':self.__name__, 'values':', '.join(self.values)} 
    return '{name}({values})'.format(**args) 

    def to_str(self, index): 
    return self.values[index] 

class Animal(object): 
    __metaclass__ = EnumMeta 
    values = ['Horse','Dog','Cat'] 

Zastosowanie:

In [1]: Animal.to_str(Animal.Dog) 
Out[1]: 'Dog' 
In [2]: Animal.Dog 
Out[2]: 1 
In [3]: str(Animal) 
Out[3]: 'Animal(Horse, Dog, Cat)' 

To proste i lekkie. Czy są jakieś wady tego podejścia?

EDYTOWANIE: Wyliczenia AFAIK nie są zbyt pytonowe jako koncepcja, dlatego nie zostały one zaimplementowane w pierwszej kolejności. Nigdy ich nie używałem i nie widzę dla nich żadnego zastosowania w Pythonie. Wyliczenia są przydatne w językach ze statycznymi typami, ponieważ nie są dynamiczne;)

0

Nie powinieneś wymagać twardego kodowania swoich wartości w klasie - lepiej mieć fabrykę wyliczników. natomiast na to, po prostu dodać kilka nicetirs przewidziane przez Python, na przykład zastąpić metodę represntation lub atrybut dostaniem:

class Enumerator(object): 
    def __init__(self, *names): 
     self._values = dict((value, index) for index, value in enumerate (names)) 
    def __getattribute__(self, attr): 
     try: 
      return object.__getattribute__(self,"_values")[attr] 
     except KeyError: 
      return object.__getattribute__(self, attr) 
    def __getitem__(self, item): 
     if isinstance (item, int): 
      return self._values.keys()[self._values.values().index(item)] 
     return self._values[item] 
    def __repr__(self): 
     return repr(self._values.keys()) 

Teraz wystarczy użyć tego:

>>> enum = Enumerator("val1", "val2", "val3") 
>>> enum 
['val3', 'val2', 'val1'] 
>>> enum.val2 
1 
>>> enum["val1"] 
0 
>>> enum[2] 
'val3' 

(btw, ludzie w Lista programistów Pythona mówi o tym, najprawdopodobniej będziemy mieć bardziej kompletne i posiadające wystarczające funkcje, implementację tego poprzez Python 3.3)

3

To zrobi to, co chcesz i generalizuje twój slig implementacji htly zmniejszenie kod kocioł-płyty:

class EnumBase: # base class of all Enums 
    @classmethod 
    def tostring(cls, value): 
     return dict((v,k) for k,v in cls.__dict__.iteritems())[value] 

    @classmethod 
    def fromstring(cls, name): 
     return cls.__dict__[name] 

class MyEnum(EnumBase): VAL1, VAL2, VAL3 = range(3) 

print MyEnum.fromstring('VAL1') 
# 0 
print MyEnum.tostring(1) 
# VAL2 
18

[Czas mija ...]

Nowy Python Enum w końcu wylądował w 3.4 i has also been backported. Tak więc odpowiedź na twoje pytanie polega teraz na tym. :)


Przykład:

>>> from enum import Enum 
>>> class Modes(Enum) : 
... Mode1 = "M1" 
... Mode2 = "M2" 
... Mode3 = "M3" 
... 

>>> Modes.Mode1 
<Modes.Mode1: 'M1'> 

>>> Modes.Mode1.value 
'M1' 

>>> Modes.Mode1.value 
'M1' 

>>> Modes['Mode1'] # index/key notation for name lookup 
<Modes.Mode1: 'M1'> 

>>> Modes('M1')  # call notation for value lookup 
<Modes.Mode1: 'M1'> 

>>> Modes("XXX")  # example error 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "C:\Anaconda3\lib\enum.py", line 291, in __call__ 
    return cls.__new__(cls, value) 
    File "C:\Anaconda3\lib\enum.py", line 533, in __new__ 
    return cls._missing_(value) 
    File "C:\Anaconda3\lib\enum.py", line 546, in _missing_ 
    raise ValueError("%r is not a valid %s" % (value, cls.__name__)) 
ValueError: 'XXX' is not a valid Modes