Класс Python Enum (с tostring из строки) - PullRequest
8 голосов
/ 17 декабря 2010

Я нашел простой способ реализовать (взломать) перечисление в Python:

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

Затем я могу назвать это так:

bob = MyEnum.VAL1

Sexy!

Хорошо, теперь я хочу иметь возможность получить как числовое значение, если дана строка, так и строку, если дано числовое значение. Допустим, я хочу, чтобы строки точно совпадали с ключом Enum

Лучшее, что я мог придумать, это что-то вроде этого:

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

или что-то в этом роде (игнорируйте, как я ловлю недействительные случаи)

Есть ли лучший, более питоничный способ сделать то, что я делаю выше? Или вышеупомянутое уже настолько кратко, насколько это возможно.

Кажется, должен быть лучший способ сделать это.

Ответы [ 7 ]

19 голосов
/ 23 октября 2013

[Время проходит ...]

Новый Python Enum наконец-то появился в версии 3.4, а также был перенесен . Таким образом, ответ на ваш вопрос теперь использовать это. :)


Пример:

>>> 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
10 голосов
/ 17 декабря 2010

Ну, вот что вы просили:

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)

Но я действительно не понимаю смысла Enums в Python.У него такая богатая система типов, а также генераторы и сопрограммы для управления состояниями.

Я знаю, что я не использую Enums в Python более 12 лет, возможно, вы тоже можете от них избавиться; -)

7 голосов
/ 17 декабря 2010

Используйте dict:

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

Никаких классов не требуется.У Dicts ваш успех в классе, потому что 1.) они невероятно эффективны, 2.) имеют множество невероятных методов, и 3.) являются универсальной языковой конструкцией.Они также расширяемы:

MyEnum['VAL4'] = 4

Нецелесообразно реализовывать функциональность C ++ (или другого языка) в Python.Если вы обнаружите, что вы «взламываете перечисление» или что-то в этом роде, вы можете поспорить, что ферма не делает этого на языке Python.

Если вы хотите пойти противоположным путем, создайте еще один диктат.(например, {'1':'VAL1', ...}

3 голосов
/ 18 декабря 2010

Это сделает то, что вы хотите, и обобщит вашу реализацию, немного уменьшив код котельной пластины:

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
3 голосов
/ 17 декабря 2010

См: Как я могу представить Enum в Python?

Это интересно:

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']

Использование:

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)'

Это просто и легко. Есть ли у них недостатки этого подхода?

EDIT: Перечисления AFAIK не очень питонны как концепция, поэтому они не были реализованы в первую очередь. Я никогда не использовал их и не вижу ни одного варианта их использования в Python. Перечисления полезны в статических типизированных языках, потому что они не являются динамическими;)

1 голос
/ 17 декабря 2010

Вы можете использовать словари:

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")

Edit: THC4k ответы определенно лучше. Но оставил мой как пример наивной реализации.

0 голосов
/ 18 декабря 2010

Вам не нужно жестко кодировать свои значения внутри класса - вам лучше иметь фабрику перечислителей.При этом, просто добавьте некоторые nicetirs, предоставленные Python, например, переопределите метод represntation или получите атрибут:

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())

Теперь просто используйте это:

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

(кстати,люди из списка разработчиков Python говорят об этом, скорее всего, у нас будет более полная, и с достаточным количеством функций, реализация этого изначально Python 3.3)

...