Python Enum class (with tostring fromstring)

余生长醉 提交于 2019-12-18 04:49:05

问题


I've found a simply way to implement(hack) an enum into Python:

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

I can then call this as such:

bob = MyEnum.VAL1

Sexy!

Alright, now I want to be able to get both the numerical value if given a string, or a string if given a numerical value. Let's say I want the strings to exactly match up to the Enum key's

The best I could think of is something like this:

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

or something like that (ignore how i'm catching invalid cases)

Is there a better, more python centric way to do what I'm doing above? Or is the above already as concise as it gets.

It seems like there's got to be a better way to do it.


回答1:


Well, here is what you asked for:

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)

But I really don't get the point of Enums in Python. It has such a rich type system as well as generators and coroutines to manage states.

I know I've not been using Enums in Python for more than 12 years, maybe you can get rid of them too ;-)




回答2:


[Time passes...]

The new Python Enum has finally landed in 3.4, and has also been backported. So the answer to your question is now to use that. :)


An example:

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



回答3:


Use a dict:

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

No classes necessary. Dicts have your class beat because 1.) they're incredibly efficient, 2.) have a bunch of incredible methods baked in, and 3.) are a universal language construct. They're also extensible:

MyEnum['VAL4'] = 4

It's not wise to implement C++ (or another language's) functionality in Python. If you find yourself "hacking up an enum" or something of that nature, you can bet the farm you're not doing it the Python way.

If you want to go the opposite way, build another dict. (e.g. {'1':'VAL1', ...}




回答4:


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

This one is interesting:

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

Use:

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

It's simple and lightweight. Are they any disadvantages of this approach?

EDIT: AFAIK enums are not very pythonic as a concept, thats why they were not implemented in the first place. I never used them, and can't see any usecase for them in Python. Enums are useful in static typed languages, because they are not dynamic ;)




回答5:


This will do what you want and generalizes your implementation slightly reducing boiler-plate code:

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



回答6:


You could use dictionaries:

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 answers is definitely better. But left mine as an example of naive implementation.




回答7:


You should not have to hardcode your values inside the class - you better have an enumerator factory. WHile at that, just add some nicetirs provided by Python, for example, override the represntation method, or attribute getting:

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

Now just use that:

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

(btw, people in the Python developers list are talking about this,most likely we will have a more complete, and with enough features, implementation of this natively by Python 3.3)



来源:https://stackoverflow.com/questions/4472901/python-enum-class-with-tostring-fromstring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!