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