constant/enum type in stdlib
This idea is not new - but it is stalled - Last I remember it came around in Python-devel in 2010, in this thread: http://mail.python.org/pipermail/python-dev/2010-November/thread.html#105967 There is an even older PEP (PEP 354) that was rejected just for not being enough interest at the time - And it was not dismissed at all - to the contrary the last e-mail in the thread is a message from the BDLF for it to **be** ! The discussion happened in a bad moment as Python was mostly freature froozen for 3.2 - and it did not show up again for Python 3.3; The reasoning for wanting enums/ constants has been debated already - but one of the main reasons that emerge from that thread are the ability to have named constants (just like we have "True" and "False". why do I think this is needed in the stdlib, and having itin a 3rd party module is not enough? because they are an interesting thing to have, not only on the stdlib, but on several widely used Python projects that don't have other dependencies. Having a feature like this into the stdlib allow these projects to make use of it, without needing other dependencies, and moreover, users which will benefit the most out of such constants will have a wll known "constant" type which won't come as a surprise in each package he is using interactively or debugging. Most of the discussion on the 2010 thread was summed up in a message by Michael Foord in this link http://mail.python.org/pipermail/python-dev/2010-November/106063.html with some follow up here: http://mail.python.org/pipermail/python-dev/2010-November/106065.html js -><- ---------- --
On Tue, Jan 29, 2013 at 11:50 AM, Joao S. O. Bueno
This idea is not new - but it is stalled - Last I remember it came around in Python-devel in 2010, in this thread: http://mail.python.org/pipermail/python-dev/2010-November/thread.html#105967
FWIW, since that last discussion, I've switched to using strings for my special constants, dumping them in a container if I need some kind of easy validity checking or iteration. That said, an enum type may still be useful for interoperability with other systems (databases, C APIs, etc). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Tue, Jan 29, 2013 at 3:50 AM, Nick Coghlan
On Tue, Jan 29, 2013 at 11:50 AM, Joao S. O. Bueno
wrote: This idea is not new - but it is stalled - Last I remember it came around in Python-devel in 2010, in this thread:
http://mail.python.org/pipermail/python-dev/2010-November/thread.html#105967
FWIW, since that last discussion, I've switched to using strings for my special constants, dumping them in a container if I need some kind of easy validity checking or iteration.
That said, an enum type may still be useful for interoperability with other systems (databases, C APIs, etc).
I really wish there would be an enum type in Python that would make sense. ISTM this has been raised numerous times, but not one submitted a good-enough proposal. Eli
On 29 January 2013 14:00, Eli Bendersky
On Tue, Jan 29, 2013 at 3:50 AM, Nick Coghlan
wrote: On Tue, Jan 29, 2013 at 11:50 AM, Joao S. O. Bueno
wrote: This idea is not new - but it is stalled - Last I remember it came around in Python-devel in 2010, in this thread:
http://mail.python.org/pipermail/python-dev/2010-November/thread.html#105967
FWIW, since that last discussion, I've switched to using strings for my special constants, dumping them in a container if I need some kind of easy validity checking or iteration.
That said, an enum type may still be useful for interoperability with other systems (databases, C APIs, etc).
I really wish there would be an enum type in Python that would make sense. ISTM this has been raised numerous times, but not one submitted a good-enough proposal.
As I pointed above, this last discussion was coming to a good term. Bad timing and no one clearly saying, with all the words "Michael Foord, please make this into a PEP" made it fade away, I think. js -><-
Eli
Eli Bendersky wrote:
I really wish there would be an enum type in Python that would make sense. ISTM this has been raised numerous times, but not one submitted a good-enough proposal.
I think the reason the discussion petered out last time is that everyone has a slightly different idea on what an enum type should be like. A number of proposals were made, but none of them stood out as being the obviously right one to put in the std lib. Also, so far nobody has come up with a really elegant solution to the DRY problem that inevitably arises in connection with enums. Ideally you want to be able to specify the names of the enums as identifiers, and not have to write them again as strings or otherwise provide explicit values for them. That seems to be very difficult to achieve cleanly with Python syntax as it stands. -- Greg
On Tue, Jan 29, 2013 at 3:26 PM, Greg Ewing
Eli Bendersky wrote:
I really wish there would be an enum type in Python that would make sense. ISTM this has been raised numerous times, but not one submitted a good-enough proposal.
I think the reason the discussion petered out last time is that everyone has a slightly different idea on what an enum type should be like. A number of proposals were made, but none of them stood out as being the obviously right one to put in the std lib.
Also, so far nobody has come up with a really elegant solution to the DRY problem that inevitably arises in connection with enums. Ideally you want to be able to specify the names of the enums as identifiers, and not have to write them again as strings or otherwise provide explicit values for them. That seems to be very difficult to achieve cleanly with Python syntax as it stands.
Since we're discussing a new language feature, why do we have to be restricted by the existing Python syntax? We have plenty of time before 3.4 at this point. Eli
On Tue, Jan 29, 2013 at 6:45 PM, Eli Bendersky
On Tue, Jan 29, 2013 at 3:26 PM, Greg Ewing
wrote: Eli Bendersky wrote:
I really wish there would be an enum type in Python that would make sense. ISTM this has been raised numerous times, but not one submitted a good-enough proposal.
I think the reason the discussion petered out last time is that everyone has a slightly different idea on what an enum type should be like. A number of proposals were made, but none of them stood out as being the obviously right one to put in the std lib.
Also, so far nobody has come up with a really elegant solution to the DRY problem that inevitably arises in connection with enums. Ideally you want to be able to specify the names of the enums as identifiers, and not have to write them again as strings or otherwise provide explicit values for them. That seems to be very difficult to achieve cleanly with Python syntax as it stands.
Hm, if people really want to write something like color = enum(RED, WHITE, BLUE) that might still be true, but given that it's likely going to look a little more like a class definition, this doesn't look so bad, and certainly doesn't violate DRY (though it's somewhat verbose): class color(enum): RED = value() WHITE = value() BLUE = value() The Python 3 metaclass can observe the order in which the values are defined easily by setting the class dict to an OrderdDict.
Since we're discussing a new language feature, why do we have to be restricted by the existing Python syntax? We have plenty of time before 3.4 at this point.
Introducing new syntax requires orders of magnitude more convincing than a new library module or even a new builtin. -- --Guido van Rossum (python.org/~guido)
Guido van Rossum wrote:
this doesn't look so bad, and certainly doesn't violate DRY (though it's somewhat verbose):
class color(enum): RED = value() WHITE = value() BLUE = value()
The verbosity is what makes it fail the "truly elegant" test for me. And I would say that it does violate DRY in the sense that you have to write value() repeatedly for no good reason. Sure, it's not bad enough to make it unusable, but like all the other solutions, it leaves me feeling vaguely annoyed that there isn't a better way. And it *is* bad enough to make writing an enum definition into a dreary chore, rather than the pleasure it should be. -- Greg
On 30Jan2013 17:34, Greg Ewing
Cameron Simpson wrote:
How about this:
Color = enum(RED=None, WHITE=None, BLUE=None, yellow=9)
You see, this is the problem -- there are quite a number of these solutions, all about as good as each other, with none of them standing out as obviously the right choice for stdlib inclusion. Michael Foord's solution has promise, though, as it manages to eliminate *all* of the extraneous cruft and look almost like it's built into the language. Plus it has the bonus of making you go "...??? How the blazes does *that* work?" the first time you see it. :-) -- Greg
On 1/31/2013 3:17 AM, Greg Ewing wrote:
Cameron Simpson wrote:
How about this:
Color = enum(RED=None, WHITE=None, BLUE=None, yellow=9)
You see, this is the problem -- there are quite a number of these solutions, all about as good as each other, with none of them standing out as obviously the right choice for stdlib inclusion.
Michael Foord's solution has promise, though, as it manages to eliminate *all* of the extraneous cruft and look almost like it's built into the language.
Plus it has the bonus of making you go "...??? How the blazes does *that* work?" the first time you see it. :-)
Yeah, I was thinking that if it were added to stdlib, the current metaclass discussion in the reference should be augmented by referring to it as a non-toy example of metaclasses at work. -- Terry Jan Reedy
On Jan 31, 2013, at 09:19 AM, Cameron Simpson wrote:
Color = enum(RED=None, WHITE=None, BLUE=None, yellow=9)
Oh, I forgot to mention that flufl.enum has an alternative API that's fairly close to this, although it does not completely eliminate DRY[1]:
from flufl.enum import make make('Animals', ('ant', 'bee', 'cat', 'dog'))
You can also supply the elements as a 2-tuples if you want to specify the values. An example from the docs providing bit flags:
def enumiter(): ... start = 1 ... while True: ... yield start ... start <<= 1 make('Flags', zip(list('abcdefg'), enumiter()))
Cheers, -Barry [1] The first argument is currently necessary in order to give the right printed representation of the enum.
Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that: class Color(Enum): RED, WHITE, BLUE = range(3) However, it's still slightly annoying that you have to specify how many values there are in the range() call. It would be even nicer it we could just use an infinite iterator, such as class Color(Enum): RED, WHITE, BLUE = values() However, the problem here is that the unpacking bytecode anally insists on the iterator providing *exactly* the right number of items, and there is no way for values() to know when to stop producing items. So, suppose we use a slightly extended version of the iterator protocol for unpacking purposes. If the object being unpacked has an __endunpack__ method, we call it after unpacking the last value, and it is responsible for doing appopriate checking and raising an exception if necessary. Otherwise we do as we do now. The values() object can then have an __endunpack__ method that does nothing, allowing you to unpack any number of items from it. -- Greg
On Wed, 30 Jan 2013 17:58:37 +1300
Greg Ewing
Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that:
class Color(Enum): RED, WHITE, BLUE = range(3)
However, it's still slightly annoying that you have to specify how many values there are in the range() call. It would be even nicer it we could just use an infinite iterator, such as
class Color(Enum): RED, WHITE, BLUE = values()
Well, how about: class Color(Enum): values = ('RED', 'WHITE', 'BLUE') ? (replace values with __values__ if you prefer) Regards Antoine.
On 30 January 2013 07:26, Antoine Pitrou
On Wed, 30 Jan 2013 17:58:37 +1300 Greg Ewing
wrote: Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that:
class Color(Enum): RED, WHITE, BLUE = range(3)
With a Python 3 metaclass that provides default values for *looked up* entries you could have this: class Color(Enum): RED, WHITE, BLUE The lookup would create the member - with the appropriate value. Michael
However, it's still slightly annoying that you have to specify how many values there are in the range() call. It would be even nicer it we could just use an infinite iterator, such as
class Color(Enum): RED, WHITE, BLUE = values()
Well, how about:
class Color(Enum): values = ('RED', 'WHITE', 'BLUE')
?
(replace values with __values__ if you prefer)
Regards
Antoine.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
On 30 January 2013 15:22, Michael Foord
On 30 January 2013 07:26, Antoine Pitrou
wrote: On Wed, 30 Jan 2013 17:58:37 +1300 Greg Ewing
wrote: Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that:
class Color(Enum): RED, WHITE, BLUE = range(3)
With a Python 3 metaclass that provides default values for *looked up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
The lookup would create the member - with the appropriate value.
class values(dict): def __init__(self): self.value = 0 def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: value = self[key] = self.value self.value += 1 return value class EnumMeta(type): @classmethod def __prepare__(metacls, name, bases): return values() def __new__(cls, name, bases, classdict): result = type.__new__(cls, name, bases, dict(classdict)) return result class Enum(metaclass=EnumMeta): pass class Color(Enum): RED, WHITE, BLUE
Michael
However, it's still slightly annoying that you have to specify how many values there are in the range() call. It would be even nicer it we could just use an infinite iterator, such as
class Color(Enum): RED, WHITE, BLUE = values()
Well, how about:
class Color(Enum): values = ('RED', 'WHITE', 'BLUE')
?
(replace values with __values__ if you prefer)
Regards
Antoine.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
--
May you do good and not evil May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
On 1/30/2013 10:30 AM, Michael Foord wrote:
On 30 January 2013 15:22, Michael Foord
With a Python 3 metaclass that provides default values for *looked up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
The lookup would create the member - with the appropriate value.
class values(dict): def __init__(self): self.value = 0 def __getitem__(self, key):
Adding 'print(self.value, key)' here prints 0 __name__ 0 __name__ 1 RED 2 WHITE 3 BLUE (I do not understand why it is the second and not first lookup of __name__ that increments the counter, but...)
try: return dict.__getitem__(self, key) except KeyError: value = self[key] = self.value self.value += 1 return value
class EnumMeta(type):
@classmethod def __prepare__(metacls, name, bases): return values()
def __new__(cls, name, bases, classdict): result = type.__new__(cls, name, bases, dict(classdict)) return result
class Enum(metaclass=EnumMeta): pass class Color(Enum): RED, WHITE, BLUE
So RED, WHITE, BLUE are 1, 2, 3; not 0, 1, 2 as I and many readers might expect. That aside (which can be fixed), this is very nice. -- Terry Jan Reedy
On 31 January 2013 08:32, Terry Reedy
On 1/30/2013 10:30 AM, Michael Foord wrote:
On 30 January 2013 15:22, Michael Foord
With a Python 3 metaclass that provides default values for *looked
up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
The lookup would create the member - with the appropriate value.
class values(dict): def __init__(self): self.value = 0 def __getitem__(self, key):
So RED, WHITE, BLUE are 1, 2, 3; not 0, 1, 2 as I and many readers might expect. That aside (which can be fixed), this is very nice.
Here is a version that I think creates an enum with most of the features of traditional and modern enums. - Enum values are subclasses of int; - Only need to declare the enum key name; - Starts at zero by default; - Can change the start value; - Can have discontiguous values (e.g. 0, 1, 5, 6); - Can have other types of class attributes; - Ensures that there is a 1:1 mapping between key:value (throws an exception if either of these is violated; - Able to obtain the keys, values and items as per the mapping interface (sorted by value); - Lookup an enum by key or value; One thing to note is that *any* class attribute assigned a value which implements __index__ will be considered an enum value assignment. I've done some funky stuff to ensure that you can access all the above either via the enum class, or by an instance of the enum class. Most of the time you would just use the Enum subclass directly (i.e. it's a namespace) but there may be use cases for having instances of the Enum classes. import collections import operator class EnumValue(int): def __new__(cls, key, value): e = super().__new__(cls, value) super().__setattr__(e, 'key', key) return e def __setattr__(self, key, value): raise TypeError("Cannot set attribute of type %r" % (type(self),)) def __repr__(self): return "<%s '%s': %d>" % (self.__qualname__, self.key, self) class EnumValues(collections.OrderedDict): def __init__(self): super().__init__() self.value = 0 self.sealed = False def __getitem__(self, key): try: obj = super().__getitem__(key) if not self.sealed and isinstance(obj, EnumValue): raise TypeError("Duplicate enum key '%s' with values: %d and %d" % (obj.key, obj, self.value)) return obj except KeyError: if key[:2] == '__' and key[-2:] == '__': raise value = self.value super().__setitem__(key, EnumValue(key, value)) self.value += 1 return value def __setitem__(self, key, value): if key[:2] == '__' and key[-2:] == '__': return super().__setitem__(key, value) try: if isinstance(value, EnumValue): assert value.key == key else: value = operator.index(value) except TypeError: return super().__setitem__(key, value) try: o = super().__getitem__(key) if isinstance(o, EnumValue): raise TypeError("Duplicate enum key '%s' with values: %d and %d" % (o.key, o, value)) except KeyError: self.value = value + 1 if isinstance(value, EnumValue): value = value else: value = EnumValue(key, value) super().__setitem__(value.key, value) class EnumMeta(type): @classmethod def __prepare__(metacls, name, bases): return EnumValues() def __new__(cls, name, bases, classdict): classdict.sealed = True result = type.__new__(cls, name, bases, dict(classdict)) enum = [] for v in classdict.values(): if isinstance(v, EnumValue): enum.append(v) enum.sort() result._key_to_enum = collections.OrderedDict() result._value_to_enum = collections.OrderedDict() for e in enum: if e in result._value_to_enum: raise TypeError("Duplicate enum value %d for keys: '%s' and '%s'" % (e, result._value_to_enum[e].key), e.key) if e.key in result._key_to_enum: raise TypeError("Duplicate enum key '%s' with values: %d and %d" % (e.key, result._key_to_enum[e.key]), e) result._key_to_enum[e.key] = e result._value_to_enum[e] = e return result def __getitem__(self, key): try: key = operator.index(key) except TypeError: return self._key_to_enum[key] else: return self._value_to_enum[key] def _items(self): return self._key_to_enum.items() def _keys(self): return self._key_to_enum.keys() def _values(self): return self._key_to_enum.values() def items(self): return self._items() def keys(self): return self._keys() def values(self): return self._values() class Enum(metaclass=EnumMeta): def __getitem__(self, key): cls = type(self) return type(cls).__getitem__(cls, key) def items(cls): return cls._items() def keys(cls): return cls._keys() def values(cls): return cls._values() Enum.items = classmethod(Enum.items) Enum.keys = classmethod(Enum.keys) Enum.values = classmethod(Enum.values) class Color(Enum): RED, WHITE, BLUE GREEN = 4 YELLOW ORANGE = 'orange' BLACK def dump(self): print(self.RED, self.WHITE, self.BLUE, self.GREEN, self.YELLOW, self.BLACK, self.ORANGE, self.dump) print(Color.RED, Color.WHITE, Color.BLUE, Color.GREEN, Color.YELLOW, Color.BLACK, Color.ORANGE, Color.dump) Color().dump() print(repr(Color.RED)) print(repr(Color['RED'])) print(repr(Color().RED)) print(repr(Color()['RED'])) print(repr(Color[0])) print(repr(Color()[0])) print(*Color.items()) print(*Color().items()) print(*Color.keys()) print(*Color().keys()) print(*Color.values()) print(*Color().values()) Tim Delaney
On 31 January 2013 12:27, Tim Delaney
On 31 January 2013 08:32, Terry Reedy
wrote: On 1/30/2013 10:30 AM, Michael Foord wrote:
On 30 January 2013 15:22, Michael Foord
With a Python 3 metaclass that provides default values for *looked
up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
The lookup would create the member - with the appropriate value.
class values(dict): def __init__(self): self.value = 0 def __getitem__(self, key):
So RED, WHITE, BLUE are 1, 2, 3; not 0, 1, 2 as I and many readers might expect. That aside (which can be fixed), this is very nice.
Here is a version that I think creates an enum with most of the features of traditional and modern enums.
- Enum values are subclasses of int;
- Only need to declare the enum key name;
- Starts at zero by default;
- Can change the start value;
- Can have discontiguous values (e.g. 0, 1, 5, 6);
- Can have other types of class attributes;
- Ensures that there is a 1:1 mapping between key:value (throws an exception if either of these is violated;
- Able to obtain the keys, values and items as per the mapping interface (sorted by value);
- Lookup an enum by key or value;
One thing to note is that *any* class attribute assigned a value which implements __index__ will be considered an enum value assignment.
Forgot about making it iterable - an easy-to-ad feature. Obviously it would iterate over the EnumValue instancess. Thought I'd better make it explicit as well that this was based on Michael Foords brilliant work. Tim Delaney
Wow - this ended up being more difficult that I'd anticipated. Ensuring
that decorators work in Michael Foord-inspired enums, etc mandated a fairly
big redesign. As it was you couldn't do something like:
class A():
a = 1
class B(Enum):
B = A.a
because when the name 'A' was resolved in the definition of class B, it was
returning an int (the next enum value).
The end result may well still have some holes, but it's looking pretty good
to me.
Enums are constructed like:
class Color(Enum):
RED, GREEN, BLUE
CYAN = 10
MAGENTA
YELLOW
BLACK
where the assigned enum starts a new count i.e. the above is 0, 1, 2, 10,
11, 12, 13. Arbitrary attributes may be assigned and not contribute to the
enumeration so long as the object being assigned does not implement
__index__ (if it does, it creates a discontiguous enumeration).
#!/usr/bin/env python3
import builtins
import collections
import operator
class EnumValue(int):
def __new__(cls, key, value):
e = super().__new__(cls, value)
super().__setattr__(e, 'key', key)
super().__setattr__(e, 'owner', None)
return e
def __setattr__(self, key, value):
raise TypeError("Cannot set attribute of type %r" % (type(self),))
def __str__(self):
return "%s.%s" % (self.owner.__name__, self.key)
def __repr__(self):
if self.owner is not None:
return "<%s '%s.%s': %d>" % (self.__qualname__,
self.owner.__qualname__, self.key, int(self))
return "<%s '%s': %d>" % (self.__qualname__, self.key, int(self))
class EnumProxy(object):
def __init__(self, key, value=None):
self.key = key
self.values = [value]
self.used = False
def __repr__(self):
return "<%s '%s': %s>" % (self.__qualname__, self.key, self.values)
def _get(self, used=None):
if used:
self.used = True
try:
return locals()[self.key]
except KeyError:
try:
return globals()[self.key]
except KeyError:
try:
return getattr(builtins, self.key)
except KeyError:
raise NameError(self.key, self.values)
def __call__(self, *p, **kw):
return self._get(True)(*p, **kw)
def __getattr__(self, name):
return getattr(self._get(True), name)
class EnumValues(collections.OrderedDict):
def __init__(self):
super().__init__()
self.sealed = False
def __getitem__(self, key):
try:
obj = super().__getitem__(key)
if not self.sealed and isinstance(obj, EnumProxy):
obj.values.append(None)
return obj
except KeyError:
# Don't do anything with __dunder__ attributes
if key[:2] == '__' and key[-2:] == '__':
raise
proxy = EnumProxy(key, None)
super().__setitem__(key, proxy)
return proxy
def __setitem__(self, key, value):
if key[:2] == '__' and key[-2:] == '__':
return super().__setitem__(key, value)
try:
if isinstance(value, EnumProxy):
value = value._get(True)
elif not isinstance(value, EnumValue):
value = operator.index(value)
except TypeError:
return super().__setitem__(key, value)
try:
o = super().__getitem__(key)
if isinstance(o, EnumProxy):
o.values.append(value)
except KeyError:
if isinstance(value, EnumProxy):
int.__setattr__(value, 'key', key)
else:
value = EnumProxy(key, value)
super().__setitem__(value.key, value)
class EnumMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
return EnumValues()
def __new__(cls, name, bases, classdict):
classdict.sealed = True
del_list = []
for v in classdict.values():
if isinstance(v, EnumProxy) and v.used:
del_list.append(v)
for v in del_list:
del classdict[v.key]
result = type.__new__(cls, name, bases, dict(classdict))
value = 0
keys = {}
values = {}
for v in classdict.values():
if isinstance(v, EnumProxy) and not v.used:
if len(v.values) > 1:
raise AttributeError("Duplicate enum key '%s.%s'" %
(result.__qualname__, v.key,))
if v.values[0] is not None:
value = v.values[0]
if isinstance(value, EnumValue):
if (value.key is not None) and (value.key != v.key):
raise AttributeError("Assigned enum value to
non-matching key '%s': %r" % (v.key, value))
if value.owner is not None:
raise AttributeError("Assigned owned enum value to
key '%s': %r" % (v.key, value))
int.__setattr__(value, 'key', v.key)
v = value
else:
v = EnumValue(v.key, value)
setattr(result, v.key, v)
value += 1
if isinstance(v, EnumValue):
int.__setattr__(v, 'owner', result)
if v in values:
raise AttributeError("Duplicate enum value %d for keys:
'%s' and '%s'" % (int(v), values[v].key, v.key))
keys[v.key] = v
values[v] = v
enum = sorted(values)
result._key_to_enum = collections.OrderedDict()
result._value_to_enum = values
for e in enum:
result._key_to_enum[e.key] = e
return result
def __getitem__(self, key):
try:
key = operator.index(key)
except TypeError:
return self._key_to_enum[key]
else:
return self._value_to_enum[key]
def _items(self):
return self._key_to_enum.items()
def _keys(self):
return self._key_to_enum.keys()
def _values(self):
return self._key_to_enum.values()
def items(self):
return self._items()
def keys(self):
return self._keys()
def values(self):
return self._values()
def __iter__(self):
return iter(self.values())
def __repr__(self):
r = super().__repr__()
r = ['
Last version (for now). I'm really interested in people's opinions on this.
For this version I've taken some inspiration from flufl.enum (but there
remains the major difference that these enums subclass int).
- Enums are now subclassable;
- Added an Enum.make() method - a bit different to flufl.enum.make since my
enums have different semantics - each element must either be a name or a
(name, value) pair, and you can have a mix;
- Instantiating an enum now returns the appropriate EnumValue
- Enums now compare not equal with any enum that is not the same object
(but continue to compare equal with ints);
- Changed EnumValue.key -> EnumValue.name and EnumValue.owner ->
EnumValue.enum.
I didn't add __members__ as that use case is covered by having the Enum be
iterable + the immutable mapping interface.
#!/usr/bin/env python3
import builtins
import collections
import operator
class EnumValue(int):
def __new__(cls, key, value):
e = super().__new__(cls, value)
super().__setattr__(e, 'name', key)
super().__setattr__(e, 'enum', None)
return e
def __setattr__(self, key, value):
raise TypeError("can't set attribute")
def __eq__(self, other):
if isinstance(other, EnumValue):
return self is other
return int(self) == other
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return super().__hash__()
def __str__(self):
if self.enum is not None:
return "%s.%s" % (self.enum.__name__, self.name)
return self.name
def __repr__(self):
if self.enum is not None:
return "<%s '%s.%s': %d>" % (self.__qualname__,
self.enum.__qualname__, self.name, int(self))
return "<%s '%s': %d>" % (self.__qualname__, self.name, int(self))
class _EnumProxy(object):
def __init__(self, key, value=None):
self.key = key
self.values = [value]
self.used = False
def __repr__(self):
return "<%s '%s': %s>" % (self.__qualname__, self.key, self.values)
def _get(self, used=None):
if used:
self.used = True
try:
return locals()[self.key]
except KeyError:
try:
return globals()[self.key]
except KeyError:
try:
return getattr(builtins, self.key)
except KeyError:
raise NameError(self.key, self.values)
def __call__(self, *p, **kw):
return self._get(True)(*p, **kw)
def __getattr__(self, name):
return getattr(self._get(True), name)
class EnumValues(collections.OrderedDict):
def __init__(self):
super().__init__()
self.sealed = False
def __getitem__(self, key):
try:
obj = super().__getitem__(key)
if not self.sealed and isinstance(obj, _EnumProxy):
obj.values.append(None)
return obj
except KeyError:
# Don't do anything with __dunder__ attributes
if key[:2] == '__' and key[-2:] == '__':
raise
proxy = _EnumProxy(key, None)
super().__setitem__(key, proxy)
return proxy
def __setitem__(self, key, value):
if key[:2] == '__' and key[-2:] == '__':
return super().__setitem__(key, value)
try:
if isinstance(value, _EnumProxy):
value = value._get(True)
elif not isinstance(value, EnumValue):
value = operator.index(value)
except TypeError:
return super().__setitem__(key, value)
try:
o = super().__getitem__(key)
if isinstance(o, _EnumProxy):
o.values.append(value)
except KeyError:
if not isinstance(value, _EnumProxy):
value = _EnumProxy(key, value)
super().__setitem__(value.key, value)
class EnumMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
return EnumValues()
def __new__(cls, name, bases, classdict):
classdict.sealed = True
del_list = []
for v in classdict.values():
if isinstance(v, _EnumProxy) and v.used:
del_list.append(v)
for v in del_list:
del classdict[v.key]
result = type.__new__(cls, name, bases, dict(classdict))
keys = {}
values = {}
result._key_to_enum = collections.OrderedDict()
result._value_to_enum = values
value = 0
for b in result.__bases__:
if isinstance(b, EnumMeta):
keys.update(b._key_to_enum)
values.update(b._value_to_enum)
if values:
value = max(values) + 1
for v in classdict.values():
if isinstance(v, _EnumProxy) and not v.used:
if len(v.values) > 1:
raise AttributeError("Duplicate enum key '%s.%s'" %
(name, v.key,))
elif v.key in keys:
raise AttributeError("Duplicate enum key '%s.%s'
(overriding '%s')" % (result.__name__, v.key, keys[v.key]))
if v.values[0] is not None:
value = v.values[0]
if isinstance(value, EnumValue):
if (value.name is not None) and (value.name != v.key):
raise AttributeError("Assigned enum value to
non-matching key '%s': %r" % (v.key, value))
if value.enum is not None:
raise AttributeError("Assigned owned enum value to
key '%s': %r" % (v.key, value))
int.__setattr__(value, 'name', v.key)
v = value
else:
v = EnumValue(v.key, value)
setattr(result, v.name, v)
value += 1
if isinstance(v, EnumValue):
int.__setattr__(v, 'enum', result)
int_v = int(v)
if int_v in values:
raise AttributeError("Duplicate enum value %d for keys:
'%s.%s' and '%s.%s'" % (
int_v, values[int_v].enum.__name__,
values[int_v].name, result.__name__, v.name))
keys[v.name] = v
values[v] = v
enum = sorted(values)
for e in enum:
result._key_to_enum[e.name] = e
return result
def __getitem__(self, key):
try:
key = operator.index(key)
except TypeError:
return self._key_to_enum[key]
else:
return self._value_to_enum[key]
def items(self):
return self._key_to_enum.items()
def keys(self):
return self._key_to_enum.keys()
def values(self):
return self._key_to_enum.values()
def __iter__(self):
return iter(self.values())
def __repr__(self):
r = super().__repr__()
r = ['
Wow - this ended up being more difficult that I'd anticipated. Ensuring that decorators work in Michael Foord-inspired enums, etc mandated a fairly big redesign. As it was you couldn't do something like:
class A(): a = 1
class B(Enum): B = A.a
because when the name 'A' was resolved in the definition of class B, it was returning an int (the next enum value).
The end result may well still have some holes, but it's looking pretty good to me.
Enums are constructed like:
class Color(Enum): RED, GREEN, BLUE CYAN = 10 MAGENTA YELLOW BLACK
where the assigned enum starts a new count i.e. the above is 0, 1, 2, 10, 11, 12, 13. Arbitrary attributes may be assigned and not contribute to the enumeration so long as the object being assigned does not implement __index__ (if it does, it creates a discontiguous enumeration).
#!/usr/bin/env python3
import builtins import collections import operator
class EnumValue(int): def __new__(cls, key, value): e = super().__new__(cls, value) super().__setattr__(e, 'key', key) super().__setattr__(e, 'owner', None) return e
def __setattr__(self, key, value): raise TypeError("Cannot set attribute of type %r" % (type(self),))
def __str__(self): return "%s.%s" % (self.owner.__name__, self.key)
def __repr__(self): if self.owner is not None: return "<%s '%s.%s': %d>" % (self.__qualname__, self.owner.__qualname__, self.key, int(self))
return "<%s '%s': %d>" % (self.__qualname__, self.key, int(self))
class EnumProxy(object): def __init__(self, key, value=None): self.key = key self.values = [value] self.used = False
def __repr__(self): return "<%s '%s': %s>" % (self.__qualname__, self.key, self.values)
def _get(self, used=None): if used: self.used = True
try: return locals()[self.key] except KeyError: try: return globals()[self.key] except KeyError: try: return getattr(builtins, self.key) except KeyError: raise NameError(self.key, self.values)
def __call__(self, *p, **kw): return self._get(True)(*p, **kw)
def __getattr__(self, name): return getattr(self._get(True), name)
class EnumValues(collections.OrderedDict): def __init__(self): super().__init__() self.sealed = False
def __getitem__(self, key): try: obj = super().__getitem__(key)
if not self.sealed and isinstance(obj, EnumProxy): obj.values.append(None)
return obj
except KeyError: # Don't do anything with __dunder__ attributes if key[:2] == '__' and key[-2:] == '__': raise
proxy = EnumProxy(key, None) super().__setitem__(key, proxy) return proxy
def __setitem__(self, key, value): if key[:2] == '__' and key[-2:] == '__': return super().__setitem__(key, value)
try: if isinstance(value, EnumProxy): value = value._get(True)
elif not isinstance(value, EnumValue): value = operator.index(value)
except TypeError: return super().__setitem__(key, value)
try: o = super().__getitem__(key)
if isinstance(o, EnumProxy): o.values.append(value)
except KeyError: if isinstance(value, EnumProxy): int.__setattr__(value, 'key', key) else: value = EnumProxy(key, value)
super().__setitem__(value.key, value)
class EnumMeta(type):
@classmethod def __prepare__(metacls, name, bases): return EnumValues()
def __new__(cls, name, bases, classdict): classdict.sealed = True
del_list = []
for v in classdict.values(): if isinstance(v, EnumProxy) and v.used: del_list.append(v)
for v in del_list: del classdict[v.key]
result = type.__new__(cls, name, bases, dict(classdict)) value = 0 keys = {} values = {}
for v in classdict.values(): if isinstance(v, EnumProxy) and not v.used: if len(v.values) > 1: raise AttributeError("Duplicate enum key '%s.%s'" % (result.__qualname__, v.key,))
if v.values[0] is not None: value = v.values[0]
if isinstance(value, EnumValue):
if (value.key is not None) and (value.key != v.key): raise AttributeError("Assigned enum value to non-matching key '%s': %r" % (v.key, value))
if value.owner is not None: raise AttributeError("Assigned owned enum value to key '%s': %r" % (v.key, value))
int.__setattr__(value, 'key', v.key) v = value
else: v = EnumValue(v.key, value)
setattr(result, v.key, v) value += 1
if isinstance(v, EnumValue): int.__setattr__(v, 'owner', result)
if v in values: raise AttributeError("Duplicate enum value %d for keys: '%s' and '%s'" % (int(v), values[v].key, v.key))
keys[v.key] = v values[v] = v
enum = sorted(values)
result._key_to_enum = collections.OrderedDict() result._value_to_enum = values
for e in enum: result._key_to_enum[e.key] = e
return result
def __getitem__(self, key): try: key = operator.index(key) except TypeError: return self._key_to_enum[key] else: return self._value_to_enum[key]
def _items(self): return self._key_to_enum.items()
def _keys(self): return self._key_to_enum.keys()
def _values(self): return self._key_to_enum.values()
def items(self): return self._items()
def keys(self): return self._keys()
def values(self): return self._values()
def __iter__(self): return iter(self.values())
def __repr__(self): r = super().__repr__() r = ['
') return ''.join(r) def __str__(self): s = ['{']
for k, v in self.items(): if s[-1][-1:] != '{': s.append(', ')
s.extend([k, ':', str(int(v))])
s.append('}') return ''.join(s)
class Enum(metaclass=EnumMeta): def __getitem__(self, key): cls = type(self) return type(cls).__getitem__(cls, key)
@classmethod def items(cls): return cls._items()
@classmethod def keys(cls): return cls._keys()
@classmethod def values(cls): return cls._values()
def __iter__(self): return iter(self.values())
def __repr__(self): r = super().__repr__() r = r.replace('object at 0x', 'enum at 0x') r = [r[:-1], ' '] r.append(str(self)) r.append('>') return ''.join(r)
def __str__(self): return str(type(self))
if __name__ == '__main__':
class Color(Enum): RED, GREEN, BLUE ORANGE = "orange" CYAN = 10 MAGENTA YELLOW BLACK
import unittest
class TestEnum(unittest.TestCase):
EXPECTED_KEY_ORDER = ('RED', 'GREEN', 'BLUE', 'CYAN', 'MAGENTA', 'YELLOW', 'BLACK') EXPECTED_INT_VALUE_ORDER = (0, 1, 2, 10, 11, 12, 13) EXPECTED_ENUM_VALUE_ORDER = (Color.RED, Color.GREEN, Color.BLUE, Color.CYAN, Color.MAGENTA, Color.YELLOW, Color.BLACK) EXPECTED_ITEMS_ORDER = tuple(zip(EXPECTED_KEY_ORDER, EXPECTED_ENUM_VALUE_ORDER))
def test_type(self): self.assertIsInstance(Color.RED, int)
def test_class_enum_values(self): self.assertEqual(0, Color.RED) self.assertEqual(1, Color.GREEN) self.assertEqual(2, Color.BLUE) self.assertEqual(10, Color.CYAN) self.assertEqual(11, Color.MAGENTA) self.assertEqual(12, Color.YELLOW) self.assertEqual(13, Color.BLACK) self.assertEqual("orange", Color.ORANGE)
def test_instance_enum_values(self): e = Color() self.assertIs(Color.RED, e.RED) self.assertIs(Color.GREEN, e.GREEN) self.assertIs(Color.BLUE, e.BLUE) self.assertIs(Color.CYAN, e.CYAN) self.assertIs(Color.MAGENTA, e.MAGENTA) self.assertIs(Color.YELLOW, e.YELLOW) self.assertIs(Color.BLACK, e.BLACK) self.assertIs(Color.ORANGE, e.ORANGE)
def test_class_indexing(self): self.assertIs(Color.CYAN, Color['CYAN']) self.assertIs(Color.CYAN, Color[10])
def test_instance_indexing(self): e = Color() self.assertIs(Color.CYAN, e['CYAN']) self.assertIs(Color.CYAN, e[10])
def test_class_keys(self): self.assertEqual(self.EXPECTED_KEY_ORDER, tuple(Color.keys()))
def test_instance_keys(self): self.assertEqual(tuple(Color.keys()), tuple(Color().keys()))
def test_class_values(self): self.assertEqual(self.EXPECTED_INT_VALUE_ORDER, tuple(Color.values())) self.assertEqual(self.EXPECTED_ENUM_VALUE_ORDER, tuple(Color.values()))
def test_instance_values(self): self.assertEqual(tuple(Color.values()), tuple(Color().values()))
def test_class_items(self): self.assertEqual(self.EXPECTED_ITEMS_ORDER, tuple(Color.items()))
def test_instance_items(self): self.assertEqual(tuple(Color.items()), tuple(Color().items()))
def test_owner(self): for e in Color: self.assertIs(e.owner, Color)
def test_class_str(self): s = str(Color)
for e in Color: self.assertIn('%s:%d' % (e.key, int(e)), s)
def test_instance_str(self): self.assertEqual(str(Color), str(Color()))
def test_class_repr(self): r = repr(Color) self.assertIn(Color.__qualname__, r) self.assertIn(str(Color), r)
def test_instance_repr(self): e = Color() r = repr(e) self.assertIn(Color.__qualname__, r) self.assertIn('at 0x', r) self.assertIn(str(Color()), r)
def _create_duplicate_key(self): class DuplicateKey(Enum): KEY, KEY
def test_duplicate_key(self): self.assertRaises(AttributeError, self._create_duplicate_key)
def _create_duplicate_value(self): class DuplicateValue(Enum): KEY1, KEY2 = 0
def test_duplicate_value(self): self.assertRaises(AttributeError, self._create_duplicate_value)
def _assign_wrong_key(self): class WrongKey(Enum): KEY1 = EnumValue('KEY2', 0)
def test_wrong_key(self): self.assertRaises(AttributeError, self._assign_wrong_key)
def test_unnamed_key1(self): class UnnamedKey(Enum): KEY1 = EnumValue(None, 5)
self.assertEqual(UnnamedKey.KEY1, 5) self.assertIs(UnnamedKey, UnnamedKey.KEY1.owner)
def test_unnamed_key1(self): unnamed = EnumValue(None, 5)
class UnnamedKey(Enum): KEY1 = unnamed
self.assertEqual(UnnamedKey.KEY1, 5) self.assertIs(UnnamedKey.KEY1, unnamed) self.assertIs(UnnamedKey, UnnamedKey.KEY1.owner)
def _assign_wrong_owner(self): class WrongOwner(Enum): KEY1 = Color.RED
def test_wrong_owner(self): self.assertRaises(AttributeError, self._assign_wrong_owner)
unittest.main()
Tim Delaney
On Feb 01, 2013, at 03:18 PM, Tim Delaney wrote:
Last version (for now). I'm really interested in people's opinions on this. For this version I've taken some inspiration from flufl.enum (but there remains the major difference that these enums subclass int).
Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :) -Barry
On Fri, Feb 1, 2013 at 7:36 AM, Barry Warsaw
On Feb 01, 2013, at 03:18 PM, Tim Delaney wrote:
Last version (for now). I'm really interested in people's opinions on this. For this version I've taken some inspiration from flufl.enum (but there remains the major difference that these enums subclass int).
Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
I would actually prefer a place where it's easy to see the code, comment on it and fork it like Bitbucket or Github. PyPI is good for other purposes... Eli
On 1 February 2013 17:30, Eli Bendersky
On Fri, Feb 1, 2013 at 7:36 AM, Barry Warsaw
wrote: On Feb 01, 2013, at 03:18 PM, Tim Delaney wrote:
Last version (for now). I'm really interested in people's opinions on this. For this version I've taken some inspiration from flufl.enum (but there remains the major difference that these enums subclass int).
Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
I would actually prefer a place where it's easy to see the code, comment on it and fork it like Bitbucket or Github. PyPI is good for other purposes...
Indeed - there are plenty of nice enums on pypi already - my call for getting then to the stdlib is that we can get a reliable enum/constant behavior to be always there - not only for the constants in the stdlib, but for projects for which adding external dependencies would be expensive (as they currently have none) js -><-
Eli
On Fri, Feb 1, 2013 at 12:30 PM, Eli Bendersky
On Fri, Feb 1, 2013 at 7:36 AM, Barry Warsaw
wrote: Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
I would actually prefer a place where it's easy to see the code, comment on it and fork it like Bitbucket or Github. PyPI is good for other purposes...
+1 And put it on PyPI to make it more accessible. -eric
On 1 February 2013 18:09, Eric Snow
On Fri, Feb 1, 2013 at 7:36 AM, Barry Warsaw
wrote: Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
I would actually prefer a place where it's easy to see the code, comment on it and fork it like Bitbucket or Github. PyPI is good for other
On Fri, Feb 1, 2013 at 12:30 PM, Eli Bendersky
wrote: purposes... +1
And put it on PyPI to make it more accessible.
As soon as there are some more goodies in there beyond the enumerator and constants themselves. I can see that a namedtuple - like utility function could be interesting to create the enums - and a way to import the generated constatnts to the current global namespace that does not violate good pratices. I think that something like: class MyEnum(Enum): RED, GREEN, BLUE load_constants(MyEnum, globals() ) is a good solution that could respect both DRY and "explicit is better than implicit" and "Special cases aren't special enough to break the rules." js -><-
-eric _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 02/01/2013 04:53 PM, Greg Ewing wrote:
Joao S. O. Bueno wrote:
class MyEnum(Enum): RED, GREEN, BLUE
load_constants(MyEnum, globals() )
The "obvious" way to spell this would be
from MyEnum import *
but it would be challenging to make that work, I suspect. :-(
It's not too tough: 8<---- constants.py -------------------------------------------------- class Colors(object): BLACK = 0 RED = 1 GREEN = 2 BLUE = 3 __all__ = ('BLACK','RED','GREEN','BLUE') @classmethod def register(cls): import sys sys.modules['%s.%s' % (__name__, cls.__name__)] = cls() Colors.register() 8<---- constants.py -------------------------------------------------- --> from constants.Colors import * --> RED 1 --> BLUE 3 ~Ethan~
Ethan Furman wrote:
On 02/01/2013 04:53 PM, Greg Ewing wrote:
The "obvious" way to spell this would be
from MyEnum import *
but it would be challenging to make that work, I suspect. :-(
It's not too tough:
Yeah, I just took up my own challenge and came up with something similar (apologies for the Python 2): class MetaEnum(type): def __init__(self, name, bases, dict): type.__init__(self, name, bases, dict) import sys sys.modules[name] = self class Enum(object): __metaclass__ = MetaEnum class MyEnum(Enum): RED = 0 GREEN = 1 BLUE = 2 from MyEnum import * print RED print GREEN print BLUE I left off the module name so that you don't have to qualify the import. A more general version would qualify it with all but the last component of the module name, so you can import it relative to the containing module. -- Greg
On Feb 01, 2013, at 11:30 AM, Eli Bendersky wrote:
I would actually prefer a place where it's easy to see the code, comment on it and fork it like Bitbucket or Github. PyPI is good for other purposes...
Well, sure, having the code in a publicly available vcs is always a good idea. My point really was that stuff ordinarily doesn't land in the stdlib until it's lived on PyPI for a while. -Barry
On 2 February 2013 02:36, Barry Warsaw
On Feb 01, 2013, at 03:18 PM, Tim Delaney wrote:
Last version (for now). I'm really interested in people's opinions on this. For this version I've taken some inspiration from flufl.enum (but there remains the major difference that these enums subclass int).
Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
Yep - I intend to. Was just hacking away initially to see if what I wanted to achieve was feasible using Michael's metaclass as a base. It's in a Mercurial repo so I'll put it up on BitBucket in the next day or two and look at cleaning it up (documentation!) to put on PyPI. Tim Delaney
On 1 February 2013 18:28, Tim Delaney
On 2 February 2013 02:36, Barry Warsaw
wrote: On Feb 01, 2013, at 03:18 PM, Tim Delaney wrote:
Last version (for now). I'm really interested in people's opinions on this. For this version I've taken some inspiration from flufl.enum (but there remains the major difference that these enums subclass int).
Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
Yep - I intend to. Was just hacking away initially to see if what I wanted to achieve was feasible using Michael's metaclass as a base. It's in a Mercurial repo so I'll put it up on BitBucket in the next day or two and look at cleaning it up (documentation!) to put on PyPI.
Indeed . .as I said, we'd better put together a pre-PEP and making this proof of concept impement most of it before going to Pypy - There are other requisites that I think aren't taken care of that come form the 2010 thread - for example, pickle-ability. js -><-
Tim Delaney
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 2 February 2013 07:28, Tim Delaney
On 2 February 2013 02:36, Barry Warsaw
wrote: On Feb 01, 2013, at 03:18 PM, Tim Delaney wrote:
Last version (for now). I'm really interested in people's opinions on this. For this version I've taken some inspiration from flufl.enum (but there remains the major difference that these enums subclass int).
Why not package it up and put it in PyPI? Better there than sitting in an email thread of some mailing list full of crazy people. :)
Yep - I intend to. Was just hacking away initially to see if what I wanted to achieve was feasible using Michael's metaclass as a base. It's in a Mercurial repo so I'll put it up on BitBucket in the next day or two and look at cleaning it up (documentation!) to put on PyPI.
Public repository on BitBucket: https://bitbucket.org/magao/enum Feel free to raise issues there, clone and make pull requests, etc. Tim Delaney
On 4 February 2013 08:39, Tim Delaney
Public repository on BitBucket: https://bitbucket.org/magao/enum
Feel free to raise issues there, clone and make pull requests, etc.
As Eli has noted in the issues, no comments or anything except the unit tests yet. Lots of magic. Needs significant cleanup. And I'll note again in case it's not clear - this currently *only* works with Python 3.3. Tim Delaney
Hi, about this enum/const thing, The use case I like more is a class where
you know all the
instances and not just a sequence of names.
Particularly It would be nice to have custom attributes and methods besides
the value and the name.
I have my own implementation with a basic api somewhat borrowed from
flufl.enum (plus a lot of other stuff),
but with this kind of support: https://github.com/jbvsmo/makeobj
I couldn't find the best way to express enums with the current python
syntax, so I also wrote a simple
regex-parsed language to fit objects with an arbitrary level of complexity.
I think, enumeration per se
is not much more useful than just a bunch of integers... Having this kind
of control IMO is.
Although Java is not a good example of anything, they have a similar
feature. What do you people think?
João Bernardo
2013/2/3 Tim Delaney
On 4 February 2013 08:39, Tim Delaney
wrote: Public repository on BitBucket: https://bitbucket.org/magao/enum
Feel free to raise issues there, clone and make pull requests, etc.
As Eli has noted in the issues, no comments or anything except the unit tests yet. Lots of magic. Needs significant cleanup.
And I'll note again in case it's not clear - this currently *only* works with Python 3.3.
Tim Delaney
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 4 February 2013 10:53, João Bernardo
Hi, about this enum/const thing, The use case I like more is a class where you know all the instances and not just a sequence of names. Particularly It would be nice to have custom attributes and methods besides the value and the name.
I have my own implementation with a basic api somewhat borrowed from flufl.enum (plus a lot of other stuff), but with this kind of support: https://github.com/jbvsmo/makeobj
I considered it, and in fact you could almost do it with my implementation by using a custom subclass of EnumValue (except trying it has just exposed a design flaw with the whole _EnumProxy bit). Works if you create the enum in the same module as EnumValues, fails otherwise. Going to have to have a rethink. Tim Delaney
2013/2/3 Tim Delaney
On 4 February 2013 10:53, João Bernardo
wrote: Hi, about this enum/const thing, The use case I like more is a class where you know all the instances and not just a sequence of names. Particularly It would be nice to have custom attributes and methods besides the value and the name.
I have my own implementation with a basic api somewhat borrowed from flufl.enum (plus a lot of other stuff), but with this kind of support: https://github.com/jbvsmo/makeobj
I considered it, and in fact you could almost do it with my implementation by using a custom subclass of EnumValue (except trying it has just exposed a design flaw with the whole _EnumProxy bit). Works if you create the enum in the same module as EnumValues, fails otherwise. Going to have to have a rethink.
For attributes, it would probably be easy to do, but for methods you will probably need a new _EnumProxy subclass for each class. I did this with a metaclass factory. João Bernardo
On 4 February 2013 11:17, Tim Delaney
On 4 February 2013 10:53, João Bernardo
wrote: Hi, about this enum/const thing, The use case I like more is a class where you know all the instances and not just a sequence of names. Particularly It would be nice to have custom attributes and methods besides the value and the name.
I have my own implementation with a basic api somewhat borrowed from flufl.enum (plus a lot of other stuff), but with this kind of support: https://github.com/jbvsmo/makeobj
I considered it, and in fact you could almost do it with my implementation by using a custom subclass of EnumValue (except trying it has just exposed a design flaw with the whole _EnumProxy bit). Works if you create the enum in the same module as EnumValues, fails otherwise. Going to have to have a rethink.
Fixed the _EnumProxy issue (but it's a kludge - I've used sys._getframe() - there's probably a better way). I've also made it so that you can override the metaclass to return a subclass of EnumValue. Now you can do something like: Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum, EnumValue, EnumMeta
class MyEnumValue1(EnumValue): ... pass ... class MyEnumMeta1(EnumMeta): ... @classmethod ... def _create_value(cls, key, value): ... return MyEnumValue1(key, value) ... class MyEnum1(Enum, metaclass=MyEnumMeta1): ... VALUE1, ... VALUE2 ... class MyEnumValue2(EnumValue): ... pass ... class MyEnumMeta2(MyEnumMeta1): ... @classmethod ... def _create_value(cls, key, value): ... return MyEnumValue2(key, value) ... class MyEnum2(MyEnum1, metaclass=MyEnumMeta2): ... VALUE3, ... VALUE4 ... print(repr(MyEnum1))
, }> print(repr(MyEnum2)) , , , }>
Tim Delaney
On 11 February 2013 09:43, Tim Delaney
I've also made it so that you can override the metaclass to return a subclass of EnumValue.
Expanded on this a bit to simplify things. Now to use a different type you can just go: Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum, EnumValue
class MyEnumValue1(EnumValue): ... pass ... class MyEnum1(Enum, metaclass=Enum.subtype(MyEnumValue1)): ... VALUE1, ... VALUE2 ... class MyEnumValue2(EnumValue): ... pass ... class MyEnum2(MyEnum1, metaclass=MyEnum1.subtype(MyEnumValue2)): ... VALUE3, ... VALUE4 ... print(repr(MyEnum1))
, }> print(repr(MyEnum2)) , , , }>
The parameter passed to subtype() can be any callable that takes 2 parameters (key, value) and returns an instance of EnumValue. Unfortunately, I can't see any way to avoid specifying the base class twice (once as the base class, once for the metaclass=). Similarly, passing the 'value_type' parameter to Enum.make() creates an appropriate metaclass from the base class: Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum, EnumValue class MyEnumValue1(EnumValue): ... pass ... MyEnum1 = Enum.make('MyEnum1', ('VALUE1', 'VALUE2'), value_type=MyEnumValue1) print(repr(MyEnum1))
, }>
Whilst the original "use a different metaclass to produce values of a different type" was possibly feature-creep, these modifications at least make it pretty painless to use. Overall, I'm pretty happy with where this is now, except for the use of sys._getframe(). Tim Delaney
On Sun, Feb 10, 2013 at 2:43 PM, Tim Delaney
On 4 February 2013 11:17, Tim Delaney
wrote: On 4 February 2013 10:53, João Bernardo
wrote: Hi, about this enum/const thing, The use case I like more is a class where you know all the instances and not just a sequence of names. Particularly It would be nice to have custom attributes and methods besides the value and the name.
I have my own implementation with a basic api somewhat borrowed from flufl.enum (plus a lot of other stuff), but with this kind of support: https://github.com/jbvsmo/makeobj
I considered it, and in fact you could almost do it with my implementation by using a custom subclass of EnumValue (except trying it has just exposed a design flaw with the whole _EnumProxy bit). Works if you create the enum in the same module as EnumValues, fails otherwise. Going to have to have a rethink.
Fixed the _EnumProxy issue (but it's a kludge - I've used sys._getframe() - there's probably a better way). I've also made it so that you can override the metaclass to return a subclass of EnumValue.
Now you can do something like:
Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum, EnumValue, EnumMeta
class MyEnumValue1(EnumValue): ... pass ... class MyEnumMeta1(EnumMeta): ... @classmethod ... def _create_value(cls, key, value): ... return MyEnumValue1(key, value) ... class MyEnum1(Enum, metaclass=MyEnumMeta1): ... VALUE1, ... VALUE2 ... class MyEnumValue2(EnumValue): ... pass ... class MyEnumMeta2(MyEnumMeta1): ... @classmethod ... def _create_value(cls, key, value): ... return MyEnumValue2(key, value) ... class MyEnum2(MyEnum1, metaclass=MyEnumMeta2): ... VALUE3, ... VALUE4 ... print(repr(MyEnum1))
, }> print(repr(MyEnum2)) , , , }>
Can you elaborate on the utility of this feature? What realistic use cases do you see for it? I think that at this point it's important to weigh all benefits of features vs. implementation complexity, and there's absolutely no need to support every feature every other enum implementation has. I want to stress again that the most important characteristic of your implementation is the clean syntax which means that enums are so easy to define they don't really need special Python syntax and a library feature can do. However, there's a big leap from this to defining custom metaclasses for enums. Eli
On 12 February 2013 00:28, Eli Bendersky
On Sun, Feb 10, 2013 at 2:43 PM, Tim Delaney
wrote: On 4 February 2013 11:17, Tim Delaney
wrote: On 4 February 2013 10:53, João Bernardo
wrote: Particularly It would be nice to have custom attributes and methods besides the value and the name.
I've also made it so that you can override the metaclass to return a subclass of EnumValue.
Can you elaborate on the utility of this feature? What realistic use cases do you see for it? I think that at this point it's important to weigh all benefits of features vs. implementation complexity, and there's absolutely no need to support every feature every other enum implementation has. I want to stress again that the most important characteristic of your implementation is the clean syntax which means that enums are so easy to define they don't really need special Python syntax and a library feature can do. However, there's a big leap from this to defining custom metaclasses for enums.
The custom metaclass is purely a mechanism to make it easy to use a custom class for the enum value. It wold be possible to manually assign a custom enum type to each enum, but then you wold lose the ability to just define the names. By using a custom metaclass, you can have it automatically assign the enum value type that you want. My next email specifies a simplified syntax for specifying the custom metaclass (you don't need to create one at all). Supporting this functionality was actually very simple. However, I am wondering though how useful this is without being able to specify additional parameters for the enums. I've often used enums with quite complex behaviour (e.g. in java) - the enum part is purely to have a unique value assigned to each instance of the class and not repeating myself for no good reason. I couldn't quite do the same with this as it currently is - whilst I can have whatever behaviour I want, there's nothing to key it off except the name and value which probably isn't enough. I've been trying to think of a syntax which would work to pass additional parameters and I can't think of anything cleaner than having a specialised class to pass the additional parameters - but I need to somehow be able to specify the optional integer value of the enum. Maybe have the first parameter be None? class EnumParams(): def __init__(self, value=None, *p, **kw): self.value = value .... def _resolve_proxies(self): # names would have been affected by EnumValues.__getattr__ - need to resolve them from _EnumProxy to the actual values ... class MyEnumValue(EnumValue): def __new__(cls, key, value, *p): e = super().__new__(cls, key, value) e.p = p def dump(self): print(self.p) class MyEnum(Enum, metaclass=Enum.subtype(MyEnumValue)): A = EnumParams(None, 'extra', 'params') B = EnumParams(3, 'more', 'params') Thoughts? Is the extra complexity worth it? The thing is, this doesn't take away from the ability to specify the very simple clean enums - but it would give the enums pretty much the full capabilities of java enums. I'd like to have all these features available so that any PEP could reference them and discuss the pros and cons (including how well they work in practice). Tim Delaney
And I've just realised that my enums will be broken when assigning a literal containing a name lookup e.g. v = 1 class MyEnum(Enum): A, B other_attr = (v,) other_attr will have an _EnumProxy instance. But I think I can do away with the _EnumProxy entirely now that I'm using sys._getframe(). Let me try something. Tim Delaney
On 12 February 2013 07:11, Tim Delaney
And I've just realised that my enums will be broken when assigning a literal containing a name lookup e.g.
v = 1
class MyEnum(Enum): A, B other_attr = (v,)
other_attr will have an _EnumProxy instance. But I think I can do away with the _EnumProxy entirely now that I'm using sys._getframe(). Let me try something.
Too early in the morning. The above would avoid the whole _EnumProxy because 'v' would be successfully looked up in the global namespace. But I may still get rid of some instances of _EnumProxy. Tim Delaney
On 12 February 2013 07:09, Tim Delaney
Supporting this functionality was actually very simple. However, I am wondering though how useful this is without being able to specify additional parameters for the enums. I've often used enums with quite complex behaviour (e.g. in java) - the enum part is purely to have a unique value assigned to each instance of the class and not repeating myself for no good reason. I couldn't quite do the same with this as it currently is - whilst I can have whatever behaviour I want, there's nothing to key it off except the name and value which probably isn't enough. I've been trying to think of a syntax which would work to pass additional parameters and I can't think of anything cleaner than having a specialised class to pass the additional parameters - but I need to somehow be able to specify the optional integer value of the enum. Maybe have the first parameter be None?
OK - I've implemented this. EnumValue has grown 'args' and 'kwargs' attributes. Enums can be constructed like: Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum, EnumParams, _
class MyEnum(Enum): ... A, B ... C = EnumParams(None, 'i', 'j', k='k', l='l') ... D = EnumParams(10, 'm', n='n') ... E ... F = EnumParams(None, 'o') ... G = 20 ... H, I ... J = EnumParams(None, p='p') ... print(repr(MyEnum))
, , , , , , , , , }>
_ is aliased to EnumParams so you can do:
class MyEnum(Enum):... A, B ... C = _(None, 'i', 'j', k='k', l='l') ... D = _(10, 'm', n='n') ... E ... F = _(None, 'o') ... G = 20 ... H, I ... J = _(None, p='p') ... print(repr(MyEnum))
, , , , , , , , , }>
which looks a bit cleaner to me, but might be a little confusing. Thoughts? Tim Delaney
I'm evil. I just had a very bad thought, and I think I kinda like it. Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum, _
class Color(Enum): ... RED ... GREEN = _(None, 'green') ... BLUE = _(None, 'blue', hex='0000FF') ... def __init__(self): ... print('__init__', repr(self), self.args, self.kwargs) ... def dump(self): ... print(self, self.args, self.kwargs) ... __init__
() {} __init__ ('green',) {} __init__ ('blue',) {'hex': '0000FF'}
print(repr(Color))
, , }> for e in Color: ... e.dump() ... Color(e).dump() ... Color.RED () {} Color.RED () {} Color.GREEN ('green',) {} Color.GREEN ('green',) {} Color.BLUE ('blue',) {'hex': '0000FF'} Color.BLUE ('blue',) {'hex': '0000FF'}
When you request an attribute on the EnumValue that doesn't exist, it gets it from the owning Enum class. If the attribute is an instance method (or static method - they have the same type) then it gets turned into an instance method of the EnumValue. Also, when the EnumValue becomes owned by Enum, Enum.__init__ is called as an EnumValue instance method (with no parameters). This actually makes a kind of perverse sense. Conceptually the actual EnumValues are the instances of the Enum (and in fact, I've made them pretend to actually be so). I think it also entirely does away with the need for subclasses of EnumValue and EnumMeta - you can do everything by specifying attributes and behaviour in the Enum instance methods. I've pushed this to https://bitbucket.org/magao/enum - have a play and see how evil you think this is. Tim Delaney
On Tue, Feb 12, 2013 at 7:06 PM, Tim Delaney
This actually makes a kind of perverse sense. Conceptually the actual EnumValues are the instances of the Enum (and in fact, I've made them pretend to actually be so).
Not so perverse. I think that makes very good sense (but then, I know C++ enums, so maybe I'm tainted). Looks good, though I've not actually played with the code. ChrisA
On 13 February 2013 08:36, Chris Angelico
On Tue, Feb 12, 2013 at 7:06 PM, Tim Delaney
wrote: This actually makes a kind of perverse sense. Conceptually the actual EnumValues are the instances of the Enum (and in fact, I've made them pretend to actually be so).
Not so perverse. I think that makes very good sense (but then, I know C++ enums, so maybe I'm tainted). Looks good, though I've not actually played with the code.
And more ... Firstly, handles more edge cases that might have been mis-identified as enum values. EnumParams is now redundant (and will probably be removed), replaced by calling the (undefined) enum value name. Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum class MyEnum(Enum): ... A, B ... C(None, 'c1', c2='2') ... D(10, 'd') ... E(None, e='e') ... repr(MyEnum) "
, , , , }>" str(MyEnum) '{A:0, B:1, C:2, D:10, E:11}' MyEnum.A.args, MyEnum.A.kwargs ((), {}) MyEnum.C.args, MyEnum.C.kwargs (('c1',), {'c2': '2'})
Tim Delaney
On 13 February 2013 12:51, Tim Delaney
And more ...
And inspired by the other thread, Ellipsis (...) is now used to indicate "use the next value". Ellipsis can either be assigned to an enum value, or passed as the first parameter to EnumParams or when calling the enum value during construction. None is no longer valid. Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from enum import Enum class MyEnum(Enum): ... A ... B, C, D = ..., 5, ... ... E = ... ... F(..., 'f') ... MyEnum
, , , , , }>
I'm stopping now. Tim Delaney
On 13 February 2013 02:42, Barry Warsaw
On Feb 12, 2013, at 05:49 PM, Tim Delaney wrote:
_ is aliased to EnumParams so you can do:
_() is very commonly used for gettext translations, by long established convention. This is convention is inherited by Python from GNU gettext.
Good point - it's actually what inspired me to use it, but a bad idea for me to do so. I've taken that out. Tim Delaney
On Feb 11, 2013, at 5:28 AM, Eli Bendersky
Can you elaborate on the utility of this feature? What realistic use cases do you see for it? I think that at this point it's important to weigh all benefits of features vs. implementation complexity, and there's absolutely no need to support every feature every other enum implementation has. I want to stress again that the most important characteristic of your implementation is the clean syntax which means that enums are so easy to define they don't really need special Python syntax and a library feature can do. However, there's a big leap from this to defining custom metaclasses for enums.
Well said. I agree with you critique. In the absence of compelling use cases, the language is better-off without a complicated new feature. Raymond
On 13 February 2013 14:25, Raymond Hettinger
On Feb 11, 2013, at 5:28 AM, Eli Bendersky
wrote: Can you elaborate on the utility of this feature? What realistic use cases do you see for it? I think that at this point it's important to weigh all benefits of features vs. implementation complexity, and there's absolutely no need to support every feature every other enum implementation has. I want to stress again that the most important characteristic of your implementation is the clean syntax which means that enums are so easy to define they don't really need special Python syntax and a library feature can do. However, there's a big leap from this to defining custom metaclasses for enums.
Well said. I agree with you critique. In the absence of compelling use cases, the language is better-off without a complicated new feature.
Absolutely. At the moment my implementation has everything in there as I've been incrementally finding syntactically cleaner ways of doing things. I intend to remove a lot of the extra functionality eventually - for example, supporting EnumMeta/EnumValue subclassing. In any case, it looks like Guido is strongly heading towards flufl.enum - doesn't mean I won't keep working on my implementation, but it doesn't look like it will be the basis for a stdlib enum. Tim Delaney
On Sun, Feb 3, 2013 at 3:53 PM, João Bernardo
Hi, about this enum/const thing, The use case I like more is a class where you know all the instances and not just a sequence of names. Particularly It would be nice to have custom attributes and methods besides the value and the name.
I have my own implementation with a basic api somewhat borrowed from flufl.enum (plus a lot of other stuff), but with this kind of support: https://github.com/jbvsmo/makeobj
I couldn't find the best way to express enums with the current python syntax, so I also wrote a simple regex-parsed language to fit objects with an arbitrary level of complexity. I think, enumeration per se is not much more useful than just a bunch of integers... Having this kind of control IMO is.
Although Java is not a good example of anything, they have a similar feature. What do you people think?
Personally, I disagree with the "more features is better" approach. Features have a cost - they complicate the implementation which makes it fragile, harder to maintain and harder to understand. Even more importantly, they make *user* code harder to understand. Therefore it's IMHO best to decide on a basic functionality that brings most of the benefits, and then think about how to make the implementation *simpler*. All I really want from an enum is what I have in C and lack in Python - a nice, minimally type-safe way to name special constants. Having this in hand, I want the simplest and cleanest syntax possible, not more features. Tim's implementation strikes a good balance - the syntax is as minimal as can be in a library implementation, and it provides all the basic features well. It adds some more, possibly at the cost of complexity, which may or may not be good. This is why I think that a PEP weighing features for/against inclusion is a logical next step. Eli
Le Wed, 30 Jan 2013 15:22:06 +0000,
Michael Foord
On 30 January 2013 07:26, Antoine Pitrou
wrote: On Wed, 30 Jan 2013 17:58:37 +1300 Greg Ewing
wrote: Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that:
class Color(Enum): RED, WHITE, BLUE = range(3)
With a Python 3 metaclass that provides default values for *looked up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
This relies on tuple evaluation order, and would also evaluate any other symbol looked up from inside the class body (which means I cannot add anything else than enum symbols to the class). In other words, I'm afraid it would be somewhat fragile ;) Regards Antoine.
On 30 January 2013 16:26, Antoine Pitrou
Le Wed, 30 Jan 2013 15:22:06 +0000, Michael Foord
a écrit : On 30 January 2013 07:26, Antoine Pitrou
wrote: On Wed, 30 Jan 2013 17:58:37 +1300 Greg Ewing
wrote: Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that:
class Color(Enum): RED, WHITE, BLUE = range(3)
With a Python 3 metaclass that provides default values for *looked up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
This relies on tuple evaluation order,
It does if you do them as a tuple.
and would also evaluate any other symbol looked up from inside the class body
Only if they aren't actually defined.
(which means I cannot add anything else than enum symbols to the class).
So not true - it is only *undefined* symbols that are added as enum values.
In other words, I'm afraid it would be somewhat fragile ;)
Well, within specific parameters... Michael
Regards
Antoine.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
On 01/30/2013 08:26 AM, Antoine Pitrou wrote:
Le Wed, Michael Foord a écrit :
With a Python 3 metaclass that provides default values for *looked up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
This relies on tuple evaluation order, and would also evaluate any other symbol looked up from inside the class body (which means I cannot add anything else than enum symbols to the class).
Probably a dumb question, but why would you want to add non-enum to an enum class? ~Ethan~
On Wed, Jan 30, 2013 at 9:19 AM, Ethan Furman
Probably a dumb question, but why would you want to add non-enum to an enum class?
class Color(Enum): RED, WHITE, BLUE def translate(language): """Get the name of an enum in the specified language.""" pass --- Bruce Follow me: http://www.twitter.com/Vroo http://www.vroospeak.com
Am 30.01.2013 17:26, schrieb Antoine Pitrou:
With a Python 3 metaclass that provides default values for *looked up* entries you could have this:
class Color(Enum): RED, WHITE, BLUE
This relies on tuple evaluation order, and would also evaluate any other symbol looked up from inside the class body (which means I cannot add anything else than enum symbols to the class).
In other words, I'm afraid it would be somewhat fragile ;)
And it breaks static code checkers like pyflakes. Georg
On 1/30/2013 2:26 AM, Antoine Pitrou wrote:
On Wed, 30 Jan 2013 17:58:37 +1300 Greg Ewing
wrote: Guido van Rossum wrote:
class color(enum): RED = value() WHITE = value() BLUE = value()
We could do somewhat better than that:
class Color(Enum): RED, WHITE, BLUE = range(3)
However, it's still slightly annoying that you have to specify how many values there are in the range() call.
For small enumerations, not much of a problem. Or, if one does not want to take the time to count, allow RED, WHITE, BLUE, _extras = range(12) # any number >= n and have a metaclass delete _extras.
Well, how about:
class Color(Enum): values = ('RED', 'WHITE', 'BLUE') ? (replace values with __values__ if you prefer)
I had the same idea, and having never written a metaclass that I can remember, decided to try it. class EnumMeta(type): def __new__(cls, name, bases, dic): for i, name in enumerate(dic['_values']): dic[name] = i del dic['_values'] return type.__new__(cls, name, bases, dic) class Enum(metaclass=EnumMeta): _values = () class Color(Enum): _values = 'RED', 'GREEN', 'BLUE' print(Color.RED, Color.GREEN, Color.BLUE)
0 1 2
So this syntax is at least feasible -- today. -- Terry Jan Reedy
Hm, if people really want to write something like
color = enum(RED, WHITE, BLUE)
that might still be true, but given that it's likely going to look a little more like a class definition, this doesn't look so bad, and certainly doesn't violate DRY (though it's somewhat verbose):
class color(enum): RED = value() WHITE = value() BLUE = value()
The Python 3 metaclass can observe the order in which the values are defined easily by setting the class dict to an OrderdDict.
Even though I agree that enums lend themselves nicely to "class"-y syntax, the example you provide shows exactly why sticking to existing syntax makes use bend over backwards. Because 'color' is really not a class. And I don't want to explicitly say it's both a class and it subclasses something called 'enum'. And I don't want to specify values when I don't need values. All I really want is: enum color: RED WHITE BLUE Or shorter: enum color: RED, WHITE, BLUE Would adding a new "enum" keyword in Python 3.4 *really* meet that much resistance? ISTM built-in, standard, enums have been on the wishlist of Python developers for a long time. Eli
Eli Bendersky, 30.01.2013 06:26:
enum color: RED, WHITE, BLUE
Would adding a new "enum" keyword in Python 3.4 *really* meet that much resistance? ISTM built-in, standard, enums have been on the wishlist of Python developers for a long time.
Special cases aren't special enough to break the rules (or even existing code!). Stefan
On Tue, Jan 29, 2013 at 6:50 AM, Nick Coghlan
FWIW, since that last discussion, I've switched to using strings for my special constants, dumping them in a container if I need some kind of easy validity checking or iteration.
Unfortunately, some of the problems with that involve unicode normalization, and won't show up in English. Python has defined a normalization for identifiers; this normalization does not apply to quoted strings. Essentially, this is the same problem string exceptions caused, except that it (sometimes) applies to '==' as well as to 'is'. Essentially, we want the simplicity of: color=enum(red, green, blue) except that we *also* want to able to compare the symbols to (int or str) constants, and to decide when they will be equal. I don't see any good way to support: color=enum(red=15, green, blue) without requiring either that strings be used instead of symbols, or that later entries be explicitly initialized. -jJ
On Jan 28, 2013, at 11:50 PM, Joao S. O. Bueno wrote:
And it was not dismissed at all - to the contrary the last e-mail in the thread is a message from the BDLF for it to **be** ! The discussion happened in a bad moment as Python was mostly freature froozen for 3.2 - and it did not show up again for Python 3.3;
I still offer up my own enum implementation, which I've used and has been available for years on PyPI, and hasn't had a new release in months because it hasn't needed one. :) It should be compatible with Pythons from 2.6 to 3.3. http://pypi.python.org/pypi/flufl.enum The one hang up about it the last time this came up was that my enum items are not ints and Guido though they should be. I actually tried at one point to make that so, but had some troublesome test failures that I didn't have time or motivation to fix, mostly because I don't particularly like those semantics. I don't remember the details. However, if someone *else* wanted to submit a branch/patch to have enum items inherit from ints, and that was all it took to have these adopted into the stdlib, I would be happy to take a look. Cheers, -Barry
On 30 January 2013 01:27, Barry Warsaw
On Jan 28, 2013, at 11:50 PM, Joao S. O. Bueno wrote:
And it was not dismissed at all - to the contrary the last e-mail in the thread is a message from the BDLF for it to **be** ! The discussion happened in a bad moment as Python was mostly freature froozen for 3.2 - and it did not show up again for Python 3.3;
I still offer up my own enum implementation, which I've used and has been available for years on PyPI, and hasn't had a new release in months because it hasn't needed one. :) It should be compatible with Pythons from 2.6 to 3.3.
http://pypi.python.org/pypi/flufl.enum
The one hang up about it the last time this came up was that my enum items are not ints and Guido though they should be. I actually tried at one point to make that so, but had some troublesome test failures that I didn't have time or motivation to fix, mostly because I don't particularly like those semantics. I don't remember the details.
However, if someone *else* wanted to submit a branch/patch to have enum items inherit from ints, and that was all it took to have these adopted into the stdlib, I would be happy to take a look.
Being an int subclass (and possibly optionally a strs subclass) is a requirement if any adopted Enum is to be used *within* the standard library in places where integers are currently used as "poor man's enums". I also don't *think* flufl.enum supports flag enums (ones that can be OR'd together), right? Michael
Cheers, -Barry
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
On Jan 30, 2013, at 03:16 PM, Michael Foord wrote:
Being an int subclass (and possibly optionally a strs subclass) is a requirement if any adopted Enum is to be used *within* the standard library in places where integers are currently used as "poor man's enums". I also don't *think* flufl.enum supports flag enums (ones that can be OR'd together), right?
Sure, it does because you have to be explicit about the enum int value to assign the item. This doesn't bother me because the syntax is clear, I almost always want an explicit int value anyway, inheritance is supported, and as you comment, flag values are (mostly) easy to support. class Colors(Enum): red = 1 green = 2 blue = 3 class MoreColors(Colors): cyan = 4 magenta = 5 # chartreuse = 2 would be an error class Flags(Enum): beautiful = 1 fast = 2 elegant = 4 wonderful = 8 Now, it's true that because Flags.fast is not an int, it must be explicitly converted to an int, e.g. `int(Flags.fast)`. That doesn't bother me. What does bother me is that Enum doesn't support automatic conversion to int for OR and AND, so you have to do this:
int(Flags.fast) | int(Flags.elegant) 6
That should be easy enough to fix by adding the appropriate operators so that you could do:
Flags.fast | Flags.elegant 6
Returning an int from such operations is the only sensible interpretation. https://bugs.launchpad.net/flufl.enum/+bug/1110501 As far as autonumbering goes, I think we could support that in Python 3.3+, though I don't have any brilliant ideas on syntax. A couple of suggestions are in this bug: https://bugs.launchpad.net/flufl.enum/+bug/1110507 e.g class Colors(Enum): red = None green = None blue = None or from flufl.enum import Enum, auto class Colors(Enum): red = auto green = auto blue = auto I'm definitely open to suggestions here! Cheers, -Barry
On Wed, Jan 30, 2013 at 7:35 AM, Barry Warsaw
On Jan 30, 2013, at 03:16 PM, Michael Foord wrote:
Being an int subclass (and possibly optionally a strs subclass) is a requirement if any adopted Enum is to be used *within* the standard library in places where integers are currently used as "poor man's enums". I also don't *think* flufl.enum supports flag enums (ones that can be OR'd together), right?
Sure, it does because you have to be explicit about the enum int value to assign the item. This doesn't bother me because the syntax is clear, I almost always want an explicit int value anyway, inheritance is supported, and as you comment, flag values are (mostly) easy to support.
class Colors(Enum): red = 1 green = 2 blue = 3
class MoreColors(Colors): cyan = 4 magenta = 5 # chartreuse = 2 would be an error
class Flags(Enum): beautiful = 1 fast = 2 elegant = 4 wonderful = 8
Now, it's true that because Flags.fast is not an int, it must be explicitly converted to an int, e.g. `int(Flags.fast)`. That doesn't bother me.
What does bother me is that Enum doesn't support automatic conversion to int for OR and AND, so you have to do this:
int(Flags.fast) | int(Flags.elegant) 6
That should be easy enough to fix by adding the appropriate operators so that you could do:
Flags.fast | Flags.elegant 6
Returning an int from such operations is the only sensible interpretation.
https://bugs.launchpad.net/flufl.enum/+bug/1110501
As far as autonumbering goes, I think we could support that in Python 3.3+, though I don't have any brilliant ideas on syntax. A couple of suggestions are in this bug:
https://bugs.launchpad.net/flufl.enum/+bug/1110507
e.g
class Colors(Enum): red = None green = None blue = None
or
from flufl.enum import Enum, auto class Colors(Enum): red = auto green = auto blue = auto
I'm definitely open to suggestions here!
Barry, since you've obviously given this issue a lot of thought, maybe you could summarize it in a PEP so we have a clear way of moving forward for 3.4 ? Eli
On Jan 30, 2013, at 08:17 AM, Eli Bendersky wrote:
Barry, since you've obviously given this issue a lot of thought, maybe you could summarize it in a PEP so we have a clear way of moving forward for 3.4 ?
I'm happy to do so if there's a realistic chance of it being accepted. We already have one rejected enum PEP (354) and we probably don't need two. ;) Cheers, -Barry
On Wed, Jan 30, 2013 at 8:27 AM, Barry Warsaw
On Jan 30, 2013, at 08:17 AM, Eli Bendersky wrote:
Barry, since you've obviously given this issue a lot of thought, maybe you could summarize it in a PEP so we have a clear way of moving forward for 3.4 ?
I'm happy to do so if there's a realistic chance of it being accepted. We already have one rejected enum PEP (354) and we probably don't need two. ;)
Reading this thread it seems that many core devs are interested in the feature and the discussion is mainly deciding on the exact semantics and implementation. Even Guido didn't really speak against it (only somewhat against adding new syntax). Eli
2013/1/30 Eli Bendersky
On Wed, Jan 30, 2013 at 8:27 AM, Barry Warsaw
wrote: On Jan 30, 2013, at 08:17 AM, Eli Bendersky wrote:
Barry, since you've obviously given this issue a lot of thought, maybe you could summarize it in a PEP so we have a clear way of moving forward for 3.4 ?
I'm happy to do so if there's a realistic chance of it being accepted. We already have one rejected enum PEP (354) and we probably don't need two. ;)
Reading this thread it seems that many core devs are interested in the feature and the discussion is mainly deciding on the exact semantics and implementation. Even Guido didn't really speak against it (only somewhat against adding new syntax).
Eli
Personally I'm -1 for a variety of reasons. 1) a const/enum type looks like something which is subject to personal taste to me. I personally don't like, for example, how flufl requires to define constants by using a class. It's just a matter of taste but to me module.FOO looks more "right" than module.Bar.FOO. Also "Colors.red < Colors.blue" raising an exception is something subject to personal taste. 2) introducing something like that (class-based) wouldn't help migrating the existent module-level constants we have in the stdlib. Only new projects or new stdlib modules would benefit from it. 3) other than being subject to personal taste, a const/enum type is also pretty easy to implement. For example, I came up with this: http://code.google.com/p/psutil/source/browse/trunk/psutil/_common.py?spec=svn1562&r=1524#33 ...which is sufficient for my needs. Users having different needs can do a similar thing pretty easily. 4) I'm getting the impression that the language is growing too big. To me, this looks like yet another thing that infrequent users have to learn before being able to read and understand Python code. Also consider that people lived without const/enum for 2 decades now. --- Giampaolo http://code.google.com/p/pyftpdlib/ http://code.google.com/p/psutil/ http://code.google.com/p/pysendfile/
Reading this thread it seems that many core devs are interested in the
feature and the discussion is mainly deciding on the exact semantics and implementation. Even Guido didn't really speak against it (only somewhat against adding new syntax).
Eli
Personally I'm -1 for a variety of reasons.
1) a const/enum type looks like something which is subject to personal taste to me. I personally don't like, for example, how flufl requires to define constants by using a class. It's just a matter of taste but to me module.FOO looks more "right" than module.Bar.FOO. Also "Colors.red < Colors.blue" raising an exception is something subject to personal taste.
2) introducing something like that (class-based) wouldn't help migrating the existent module-level constants we have in the stdlib. Only new projects or new stdlib modules would benefit from it.
These are more in the domain of implementation details, though, not criticizing the concep?
3) other than being subject to personal taste, a const/enum type is also pretty easy to implement. For example, I came up with this:
http://code.google.com/p/psutil/source/browse/trunk/psutil/_common.py?spec=svn1562&r=1524#33 ...which is sufficient for my needs. Users having different needs can do a similar thing pretty easily.
It is precisely *because* every library defines its own way to create enums that IMHO we should have them in the language (or in the standard library, at the least).
4) I'm getting the impression that the language is growing too big. To me, this looks like yet another thing that infrequent users have to learn before being able to read and understand Python code. Also consider that people lived without const/enum for 2 decades now.
I respectfully disagree. Most folks seem to favor a library solution (i.e. no new syntax, just a new metaclass+class to use). The stdlib has tools for very obscure things. In comparison, enum is something almost every non-trivial program needs to use at some stage or another. Eli
2013/1/30 Eli Bendersky
Reading this thread it seems that many core devs are interested in the
feature and the discussion is mainly deciding on the exact semantics and implementation. Even Guido didn't really speak against it (only somewhat against adding new syntax).
Eli
Personally I'm -1 for a variety of reasons.
1) a const/enum type looks like something which is subject to personal taste to me. I personally don't like, for example, how flufl requires to define constants by using a class. It's just a matter of taste but to me module.FOO looks more "right" than module.Bar.FOO. Also "Colors.red < Colors.blue" raising an exception is something subject to personal taste.
2) introducing something like that (class-based) wouldn't help migrating the existent module-level constants we have in the stdlib. Only new projects or new stdlib modules would benefit from it.
These are more in the domain of implementation details, though, not criticizing the concep?
Personally I'd be +0 for a constant type and -1 for an enum type, which I consider just useless. If a 'constant' type has to be added though, I'd prefer it to be as simple as possible and close to what we've been used thus far, meaning accessing it as "foo.BAR". In everybody's mind it is clear that "foo.BAR" is a constant, and that should be preserved. Something along these lines:
from collections import constant STATUS_IDLE = constant(0, 'idle', doc='refers to the idle state') STATUS_IDLE 0 str(STATUS_IDLE) 'idle'
---- Giampaolo http://code.google.com/p/pyftpdlib/ http://code.google.com/p/psutil/ http://code.google.com/p/pysendfile/
On 01/30/2013 01:52 PM, � wrote:
2013/1/30 Eli Bendersky
: These are more in the domain of implementation details, though, not criticizing the concep?
Personally I'd be +0 for a constant type and -1 for an enum type, which I consider just useless. If a 'constant' type has to be added though, I'd prefer it to be as simple as possible and close to what we've been used thus far, meaning accessing it as "foo.BAR". In everybody's mind it is clear that "foo.BAR" is a constant, and that should be preserved. Something along these lines:
from collections import constant STATUS_IDLE = constant(0, 'idle', doc='refers to the idle state') STATUS_IDLE 0 str(STATUS_IDLE) 'idle'
So you'd have something like: --> from collections import constant --> STATUS_IDLE = constant(0, 'idle', doc='refers to the idle state') --> STATUS_PAUSE = constant(1, 'pause', doc='refers to the pause state') --> STATUS_RUN = constant(2, 'run', doc='refers to the run state') ? Absolutely -1 on this. (Although you can certainly implement it now.) ~Ethan~
I'll agree that enums are subject to personal taste, and I am opinionated about the syntax and semantics, as should be evident in my library :). On Jan 30, 2013, at 09:13 PM, Giampaolo Rodolà wrote:
1) a const/enum type looks like something which is subject to personal taste to me. I personally don't like, for example, how flufl requires to define constants by using a class.
In practice, I find this quite nice. In my larger projects, I define the enum class an the interface module and often intersperse comments among the enum values so that more documentation is provided to the reader.
It's just a matter of taste but to me module.FOO looks more "right" than module.Bar.FOO.
I almost always 'from module import MyEnum' so typical use looks something like: if thing.color is Color.red: ... elif thing.color is Color.blue: ... Again, in practice, I find it quite readable and just the right level of verbosity.
Also "Colors.red < Colors.blue" raising an exception is something subject to personal taste.
I guess, if you like blue more than red, but what if you like red more than blue? :) Ordered enums just don't usually make sense, and if they really did, you can coerce to int to do the comparison (but again, I've never needed it, so YAGNI).
2) introducing something like that (class-based) wouldn't help migrating the existent module-level constants we have in the stdlib. Only new projects or new stdlib modules would benefit from it.
Sure, but I don't think this is necessarily about converting the stdlib. We rarely do such mass conversions anyway.
3) other than being subject to personal taste, a const/enum type is also pretty easy to implement.
True, depending on the semantics, syntax, and feature you want.
4) I'm getting the impression that the language is growing too big. To me, this looks like yet another thing that infrequent users have to learn before being able to read and understand Python code. Also consider that people lived without const/enum for 2 decades now.
Well, I would agree that the *language* doesn't need them, but that's different than the stdlib. Maybe the stdlib still doesn't need them either. I don't personally care either way except to save me the trouble of writing up another PEP. :) As for the language growing too big, maybe Pycon 2013 is time for another one of Guido's infamous polls! Cheers, -Barry
Le Wed, 30 Jan 2013 15:16:49 +0000,
Michael Foord
Being an int subclass (and possibly optionally a strs subclass) is a requirement if any adopted Enum is to be used *within* the standard library in places where integers are currently used as "poor man's enums". I also don't *think* flufl.enum supports flag enums (ones that can be OR'd together), right?
If a flexible solution is desired (with either int or str subclassing, various numbering schemes), may I suggest another kind of syntax: class ErrorFlag(Enum): type = 'symbolic' names = ('strict', 'ignore', 'replace') class SeekFlag(Enum): type = 'sequential' names = ('SET', 'CUR', 'END') class TypeFlag(Enum): type = 'bitmask' names = ('HEAPTYPE', 'HAS_GC', 'INT_SUBCLASS')
ErrorFlag.ignore ErrorFlag.ignore ErrorFlag.ignore == 'ignore' True ErrorFlag('ignore') ErrorFlag.ignore isinstance(ErrorFlag.ignore, str) True isinstance(ErrorFlag.ignore, int) False ErrorFlag(0) [...] ValueError: invalid value for
: 0
SeekFlag('SET') SeekFlag.SET SeekFlag('SET') + 0 0 SeekFlag(0) SeekFlag.SET isinstance(SeekFlag.CUR, int) True isinstance(SeekFlag.CUR, str) False
TypeFlag(1) TypeFlag.HEAPTYPE TypeFlag(2) TypeFlag.HAS_GC TypeFlag.HAS_GC | TypeFlag.INT_SUBCLASS 6
Regards Antoine.
On 30 January 2013 16:56, Serhiy Storchaka
On 30.01.13 18:23, Antoine Pitrou wrote:
TypeFlag.HAS_GC | TypeFlag.INT_SUBCLASS
6
I prefer something like
TypeFlag.HAS_GC | TypeFlag.INT_SUBCLASS TypeFlag.HAS_GC|INT_SUBCLASS
Indeed - the whole benefit (pretty much) of using an Enum class is that you're no longer dealing with raw ints. Michael
______________________________**_________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/**mailman/listinfo/python-ideashttp://mail.python.org/mailman/listinfo/python-ideas
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
On 01/30/2013 08:23 AM, Antoine Pitrou wrote:
If a flexible solution is desired (with either int or str subclassing, various numbering schemes), may I suggest another kind of syntax:
class ErrorFlag(Enum): type = 'symbolic' names = ('strict', 'ignore', 'replace')
class SeekFlag(Enum): type = 'sequential' names = ('SET', 'CUR', 'END')
class TypeFlag(Enum): type = 'bitmask' names = ('HEAPTYPE', 'HAS_GC', 'INT_SUBCLASS')
This I like.
ErrorFlag.ignore ErrorFlag.ignore ErrorFlag.ignore == 'ignore' True ErrorFlag('ignore') ErrorFlag.ignore isinstance(ErrorFlag.ignore, str) True isinstance(ErrorFlag.ignore, int) False ErrorFlag(0) [...] ValueError: invalid value for
: 0 SeekFlag('SET') SeekFlag.SET SeekFlag('SET') + 0 0 SeekFlag(0) SeekFlag.SET isinstance(SeekFlag.CUR, int) True isinstance(SeekFlag.CUR, str) False
TypeFlag(1) TypeFlag.HEAPTYPE TypeFlag(2) TypeFlag.HAS_GC TypeFlag.HAS_GC | TypeFlag.INT_SUBCLASS 6
This should be `TypeFlag.HEAPTYPE|HAS_GC` +1 ~Ethan~
Hello. It should be also possible to specify the values of enum constants explicitly. For 'bitmask' type only powers of 2 should be allowed or maybe the values could be the exponents (as your TypeFlag example indicates). The same way 'symbolic' type acts as str and 'sequential' type acts as int, 'bitmask' type could act both as int and set (or frozenset) since its semantics is like of set. The enum value object could represent both the int value and corresponding singleton set. OR-ing would produce corresponding multivalue set.
isinstance(TypeFlag.HEAPTYPE, int) True isinstance(TypeFlag.HEAPTYTE, set) True
TypeFlag.HAS_GC | TypeFlag.INT_SUBCLASS TypeFlag.HEAPTYPE|HAS_GS # or maybe
TypeFlag.HEAPTYPE in (TypeFlag.HEAPTYPE | TypeFlag.HAS_GC) True TypeFlag.HEAPTYPE in TypeFlag.HEAPTYPE True
TypeFlag(1) TypeFlag.HEAPTYPE TypeFlag(2) TypeFlag.HAS_GC set(TypeFlag.HEAPTYPE) {1} set(TypeFlag.HEAPTYPE | TypeFlag.HAS_GC) {1, 2} int(TypeFlag.HEAPTYPE) 2 int(TypeFlag.HEAPTYPE | TypeFlag.HAS_GC) 6
Note the difference between n and 2 ** n semantics. So there slould be something like
TypeFlag.decompose(2) TypeFlag.HEAPTYPE TypeFlag.decompose(6) TypeFlag.HEAPTYPE|HAS_GS
Regards, Drekin
I think that instead of the invention of implementation which covers all possible hypothetic scenarios, much more useful for the enums promotion would be if someone were to transform the existing constants in stdlib into enums with a minimum of the necessary capabilities. This will show which features are the most important, which interface is more convenient from user's point of view, and what difficulties have to be faced.
On 1 February 2013 20:27, Serhiy Storchaka
I think that instead of the invention of implementation which covers all possible hypothetic scenarios
The scenarios I'm really interested in here is DRY; automatic assignment (i.e. not having to specify any values); manual assignment (which should allow any expression that is legal within a class definition) and being able to introspect the enum. Everything else falls out of supporting those scenarios within the normal restrictions of enums (in particular 1:1 mapping) and conforming to obvious interfaces. Tim Delaney
On Fri, Feb 1, 2013 at 2:27 AM, Serhiy Storchaka
I think that instead of the invention of implementation which covers all possible hypothetic scenarios, much more useful for the enums promotion would be if someone were to transform the existing constants in stdlib into enums with a minimum of the necessary capabilities. This will show which features are the most important, which interface is more convenient from user's point of view, and what difficulties have to be faced.
+1 There's a lot of precedent (and good reasons) for starting with a minimal/basic implementation in the stdlib. -eric
participants (23)
-
Antoine Pitrou
-
Barry Warsaw
-
Bruce Leban
-
Cameron Simpson
-
Chris Angelico
-
drekin@gmail.com
-
Eli Bendersky
-
Eric Snow
-
Ethan Furman
-
Georg Brandl
-
Giampaolo Rodolà
-
Greg Ewing
-
Guido van Rossum
-
Jim Jewett
-
Joao S. O. Bueno
-
João Bernardo
-
Michael Foord
-
Nick Coghlan
-
Raymond Hettinger
-
Serhiy Storchaka
-
Stefan Behnel
-
Terry Reedy
-
Tim Delaney