Re: [Python-ideas] PEP for enum library type?

After all the defenses I still don't like Tim's proposed syntax. Color me Barry. On Tue, Feb 12, 2013 at 11:54 AM, Tim Delaney <tim.delaney@aptare.com> wrote:
-- --Guido van Rossum (python.org/~guido)

On Tue, Feb 12, 2013 at 11:59 AM, Guido van Rossum <guido@python.org> wrote:
After all the defenses I still don't like Tim's proposed syntax. Color me Barry.
In this case, I see no reason not to use Barry's flufl.enum - it's pretty good and has been around for a while. The main advantage of Tim's implementation syntax (wasn't it Michael Foord who originally proposed it?) is that it lets us do less typing which is great. IOW, sure with flufl.enum I can do this and be reasonably happy: class Color(Enum): RED = 1 BLUE = 2 GREEN = 3 But in 99% of the cases in Python code I absolutely don't care about the values of these enumerations (*), so I'd much, *much* rather write: class Color(Enum): RED, BLUE, GREEN Yes, the implementation is messy, but it can be documented and tested well, and it's fairly small and confined. But this is a matter of preference, and I can see your point (Guido) about preferring the more explicit syntax in order to avoid too much magic. Eli (*) And where I do care, I'd still enjoy them being generated automatically.

On Tue, 12 Feb 2013 12:58:44 -0800 Eli Bendersky <eliben@gmail.com> wrote:
I still hope enum values are strings by default - or, if not, that they get nice reprs. Integer enums are only useful for interoperability with stubborn low-level libraries :-) Regards Antoine.

On Tue, Feb 12, 2013 at 1:04 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
I agree, and Tim's implementation provides a very nice string representation, no matter what the value underneath is. I don't really care what the value underneath is, actually. Eli

On Feb 12, 2013, at 02:13 PM, Eli Bendersky wrote:
Except that sometimes you do, which is why flufl.enum has a nice concise str and a more detailed repr. The former doesn't include the underlying value while the latter does. Edible cake. -Barry

On 14 February 2013 05:14, Barry Warsaw <barry@python.org> wrote:
Yes - my enum's exactly the same - simple, concise str (for both the enum and values) and detailed repr. The actual repr is a little different to flufl.enum repr (I think the str is the same) but conveys the same information. Color.RED <EnumValue 'Color.RED': 0> {RED:0, GREEN:1, BLUE:2, CYAN:10, MAGENTA:11, YELLOW:12, BLACK:127} <enum __main__.Color {<EnumValue 'RED': 0>, <EnumValue 'GREEN': 1>, <EnumValue 'BLUE': 2>, <EnumValue 'CYAN': 10>, <EnumValue 'MAGENTA': 11>, <EnumValue 'YELLOW': 12>, <EnumValue 'BLACK': 127>}> Tim Delaney

On 13/02/13 08:04, Antoine Pitrou wrote:
I still hope enum values are strings by default - or, if not, that they get nice reprs.
99.9% of the time I don't care what value enums have, in fact it is an advantage if I don't know. Pascal, as I remember it, doesn't give you any way to find out. But I consider enums displaying as their name to be essential to an enum. -- Steven

Steven D'Aprano wrote:
Yes, it does: the ord() function.
But I consider enums displaying as their name to be essential to an enum.
That's one thing that, frustratingly, standard Pascal *doesn't* give you (although many dialects have extensions that do). -- Greg

On Feb 12, 2013, at 10:04 PM, Antoine Pitrou wrote:
>>> from flufl.enum import make >>> Colors = make('Colors', 'RED GREEN BLUE'.split()) >>> print(Colors.RED) Colors.RED >>> Colors.RED <EnumValue: Colors.RED [int=1]> Having the enum name in the str and (admittedly uglier but more informational) repr is very important to me, because I typically use enums like so: from mailman.interfaces.members import DeliveryMode ... def send_plaintext(member): ... if member.delivery_mode is DeliveryMode.regular: ... elif member.delivery_mode is DeliveryMode.mime_digests: ... Now, if something goes wrong, I can set a breakpoint or debug-print right before the conditional and get the value of `member.delivery_mode`. The fact that it will print `DeliveryMode.regular` makes debugging the problem a *much* better experience. Cheers, -Barry

On 13 February 2013 07:58, Eli Bendersky <eliben@gmail.com> wrote:
Indeed it was and I don't want to take any of the "credit" away from Michael. I simply took his original inspired idea and added several extra layers of magic on top ;) I've updated my README to make that clear. Tim Delaney

On 13 February 2013 07:58, Eli Bendersky <eliben@gmail.com> wrote:
I'm of the opinion that the most fundamental property of an enum is that it can generate its values automatically. Everything else is a nice-to-have, but without that property it's not much use as an enum. Tim Delaney

On 13 February 2013 08:59, Guido van Rossum <guido@python.org> wrote:
And I'm of the opinion that the most fundamental property of an enum is that it doesn't print as an int.
That part is easy. 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.
Making an appropriate str/repr just requires deciding what it should be. I chose to make the reprs give more information, but there's no reason they couldn't be simplified. I'll see if I can come up with a syntax for declaration that we're both happy with, but I doubt I'll be as happy with it as what I've currently got (despite its edge cases). Tim Delaney

On Feb 13, 2013, at 08:19 AM, Tim Delaney wrote:
See, I don't care about that much because I define an enum once, but I *use* it hundreds of times. Thus a little more verbosity/explicitness at definition time is a one-time cost, and a very low one at that. It might not seems that way when you're writing them in so many mailing list replies though ;). In real code, having a really good str and repr, along with good semantics makes them much more useful for debugging, and very pleasant to use. Cheers, -Barry

On 14 February 2013 05:19, Barry Warsaw <barry@python.org> wrote:
My main concern regarding this is ensuring that: 1. Values don't get repeated. 2. Values don't get skipped (unless explicitly skipped); #1 obviously is the most important property, but in practice I find #2 to be very important as well. Whilst it's more verbose, the ... syntax isn't bad, and I've added support to it to my enum to play with. Currently my enum implementation includes the kitchen sink, but I'm looking to do some renovations soon.
In real code, having a really good str and repr, along with good semantics makes them much more useful for debugging, and very pleasant to use.
Absolutely. But it's not just that either. If you can convert from the string name to the enum, it makes the enum suitable as a transport mechanism. Each end can have different values for the enum in practice so long as the names don't change - it can make things much more robust across versions. Tim Delaney

On Feb 14, 2013, at 06:28 AM, Tim Delaney wrote:
1. Values don't get repeated.
This is easy, and flufl.enum guarantees this, even for subclasses: >>> class Colors(Enum): ... red = 1 ... blue = 1 ... Traceback (most recent call last): ... TypeError: Multiple enum values: 1 >>> class Colors(Enum): ... red = 1 ... blue = 2 ... >>> class MoreColors(Colors): ... green = 2 ... Traceback (most recent call last): ... TypeError: Multiple enum values: 2
2. Values don't get skipped (unless explicitly skipped);
Is #2 is the reason why you have different subclasses for flag enums, since the definition of "skipped" is different? Many folks have said they don't care about the actual enum values, so for them, skips don't matter. Setting aside explicit skips, you can add such verification with a class decorator, e.g.: -----snip snip----- from flufl.enum import Enum def noskips(cls): for i, intval in enumerate(cls, start=1): if i != int(intval): raise TypeError('Skipped value: {}'.format(i)) return cls @noskips class GoodColors(Enum): green = 3 blue = 1 red = 2 @noskips class BadColors(Enum): green = 4 blue = 1 red = 2 $ python3 noskips.py Traceback (most recent call last): ... TypeError: Skipped value: 3 -----snip snip-----
If you can convert from the string name to the enum, it makes the enum suitable as a transport mechanism.
>>> from flufl.enum import make >>> Colors = make('Colors', 'red green blue'.split()) >>> Colors['red'] <EnumValue: Colors.red [int=1]> >>> Colors[2] <EnumValue: Colors.green [int=2]> >>> Colors[Colors.green.name] <EnumValue: Colors.green [int=2]> Cheers, -Barry

On Wed, 13 Feb 2013 13:19:14 -0500 Barry Warsaw <barry@python.org> wrote:
The cost is not low when you have many values. Also, with many values and one value per line, it can make your declaration very long vertically. And it's not always true that you use an enum much more often than you define it. For example, you may define many error codes (e.g. errnos) for compatibility with another system, but only check a few of them explicitly in your application code. Regards Antoine.

Le Wed, 13 Feb 2013 17:28:09 -0500, Barry Warsaw <barry@python.org> a écrit :
One of my common use cases for wanting an enum has always been to map a third-party protocol or library's error codes. Really, the one screaming use case in the stdlib is in the errno module :-) For that you have to be able to define an enum that subclasses int, though. Regards Antoine.

Forgive me, a lurker, but I wonder: how does an enum differ in usage from something like a Symbol from ruby, other than that an enum is required to be declared beforehand? I'm definitely a python guy, but the idea of identifiers that are their own value interests me, and seems like it could have some overlap with the concept and usage of enums. If there's not much difference in usage between enums and symbols apart from declaration, perhaps we could make the declaration optional and combine the concepts? This would certainly require parser changes. Ryan

I don't know Ruby symbols, but if they are anything like Lisp symbols, they are completely different. Lisp symbols are interned strings -- Python just uses strings (and if they happen to be interned you get a modest speedup). This is for things like: if hasattr(x, 'foo'): x.foo() On Tue, Feb 12, 2013 at 1:55 PM, <ryan@ryanhiebert.com> wrote:
-- --Guido van Rossum (python.org/~guido)

On 13 February 2013 09:29, Jonathan Slenders <jonathan@slenders.be> wrote:
That's my conclusion as well. To get a nice* enum with guaranteed semantics and no edge cases, I think it needs parser support. * for my definition of "nice". Tim Delaney

Just quickly thinking a little out of the box. Suppose, that we added grammar for scheme-like symbols. (You know, these atomic identifiers, prefixed with a quote. I don't know much about scheme, but as I understand, they are just constants.) In Python, the syntax could be similar, we could simply do in a global module scope: sym symbol which would translate to: locals()['symbol'] = Symbol('symbol') Where Symbol is a built-in that cannot change, which doesn't have a content, except for its name, and never equals anything but itself. From then on, we can access it through the name-binding 'symbol' Then we could probably create enums, using the classes as we have: class Colors: sym red sym green sym blue There are probably some gaps here in my reasoning, but this could also work. Cheers, Jonathan 2013/2/12 Tim Delaney <timothy.c.delaney@gmail.com>

Stefan Krah writes:
I'll take you at your word. But maybe you should stop reading now.<wink/>
Is a new enum keyword still a possibility? To me that seems to be the cleanest way.
If parser support were on the table, couldn't we avoid a new keyword by aping the Pascal syntax with class Color(Enum) = [RED, GREEN, BLUE] or something like that? I realize this is horrible because of the missing colon, and because availability of the syntax depends on the base class. Maybe it will suggest something to somebody, though. The base class seems to be needed to support variants like OrderedEnum and FlagEnum, as the implicit Enum class Color = [RED, GREEN, BLUE] can't disambiguate common use cases (ordered enums, or-able flags). Another possibility would be to allow the list of identifiers as the base class: class Color([RED, GREEN, BLUE]): pass I suppose this is better than the version using "=", which doesn't seem to allow explicit initialization of the enumerated ids, or operations on them, in the class body. Perhaps the Enum base class should be explicit: class Color(Enum, [RED, GREEN, BLUE]): pass In all cases the point of using lists instead of (implicit) tuples is to clearly guarantee order of evaluation to support OrderedEnum. Sets are out because FlagEnum doesn't need such support.

On 12 February 2013 19:29, Guido van Rossum <guido@python.org> wrote:
Just throwing in another syntax that is valid, requires no quotes and no magic - in case someone is interested in developing on it:
Colors = make(RED=..., GREEN=..., BLUE=...)

On Tue, Feb 12, 2013 at 11:59 AM, Guido van Rossum <guido@python.org> wrote:
After all the defenses I still don't like Tim's proposed syntax. Color me Barry.
In this case, I see no reason not to use Barry's flufl.enum - it's pretty good and has been around for a while. The main advantage of Tim's implementation syntax (wasn't it Michael Foord who originally proposed it?) is that it lets us do less typing which is great. IOW, sure with flufl.enum I can do this and be reasonably happy: class Color(Enum): RED = 1 BLUE = 2 GREEN = 3 But in 99% of the cases in Python code I absolutely don't care about the values of these enumerations (*), so I'd much, *much* rather write: class Color(Enum): RED, BLUE, GREEN Yes, the implementation is messy, but it can be documented and tested well, and it's fairly small and confined. But this is a matter of preference, and I can see your point (Guido) about preferring the more explicit syntax in order to avoid too much magic. Eli (*) And where I do care, I'd still enjoy them being generated automatically.

On Tue, 12 Feb 2013 12:58:44 -0800 Eli Bendersky <eliben@gmail.com> wrote:
I still hope enum values are strings by default - or, if not, that they get nice reprs. Integer enums are only useful for interoperability with stubborn low-level libraries :-) Regards Antoine.

On Tue, Feb 12, 2013 at 1:04 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
I agree, and Tim's implementation provides a very nice string representation, no matter what the value underneath is. I don't really care what the value underneath is, actually. Eli

On Feb 12, 2013, at 02:13 PM, Eli Bendersky wrote:
Except that sometimes you do, which is why flufl.enum has a nice concise str and a more detailed repr. The former doesn't include the underlying value while the latter does. Edible cake. -Barry

On 14 February 2013 05:14, Barry Warsaw <barry@python.org> wrote:
Yes - my enum's exactly the same - simple, concise str (for both the enum and values) and detailed repr. The actual repr is a little different to flufl.enum repr (I think the str is the same) but conveys the same information. Color.RED <EnumValue 'Color.RED': 0> {RED:0, GREEN:1, BLUE:2, CYAN:10, MAGENTA:11, YELLOW:12, BLACK:127} <enum __main__.Color {<EnumValue 'RED': 0>, <EnumValue 'GREEN': 1>, <EnumValue 'BLUE': 2>, <EnumValue 'CYAN': 10>, <EnumValue 'MAGENTA': 11>, <EnumValue 'YELLOW': 12>, <EnumValue 'BLACK': 127>}> Tim Delaney

On Feb 14, 2013, at 06:22 AM, Tim Delaney wrote:
I like that you came to the same conclusion, namely that the str should be simple and the repr more detailed. The exactly color of that detail can be bikeshedded to death now. :) -Barry

On 13/02/13 08:04, Antoine Pitrou wrote:
I still hope enum values are strings by default - or, if not, that they get nice reprs.
99.9% of the time I don't care what value enums have, in fact it is an advantage if I don't know. Pascal, as I remember it, doesn't give you any way to find out. But I consider enums displaying as their name to be essential to an enum. -- Steven

Steven D'Aprano wrote:
Yes, it does: the ord() function.
But I consider enums displaying as their name to be essential to an enum.
That's one thing that, frustratingly, standard Pascal *doesn't* give you (although many dialects have extensions that do). -- Greg

On Feb 12, 2013, at 10:04 PM, Antoine Pitrou wrote:
>>> from flufl.enum import make >>> Colors = make('Colors', 'RED GREEN BLUE'.split()) >>> print(Colors.RED) Colors.RED >>> Colors.RED <EnumValue: Colors.RED [int=1]> Having the enum name in the str and (admittedly uglier but more informational) repr is very important to me, because I typically use enums like so: from mailman.interfaces.members import DeliveryMode ... def send_plaintext(member): ... if member.delivery_mode is DeliveryMode.regular: ... elif member.delivery_mode is DeliveryMode.mime_digests: ... Now, if something goes wrong, I can set a breakpoint or debug-print right before the conditional and get the value of `member.delivery_mode`. The fact that it will print `DeliveryMode.regular` makes debugging the problem a *much* better experience. Cheers, -Barry

On 13 February 2013 07:58, Eli Bendersky <eliben@gmail.com> wrote:
Indeed it was and I don't want to take any of the "credit" away from Michael. I simply took his original inspired idea and added several extra layers of magic on top ;) I've updated my README to make that clear. Tim Delaney

On 13 February 2013 07:58, Eli Bendersky <eliben@gmail.com> wrote:
I'm of the opinion that the most fundamental property of an enum is that it can generate its values automatically. Everything else is a nice-to-have, but without that property it's not much use as an enum. Tim Delaney

And I'm of the opinion that the most fundamental property of an enum is that it doesn't print as an int. On Tue, Feb 12, 2013 at 1:19 PM, Tim Delaney <timothy.c.delaney@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido)

On 13 February 2013 08:59, Guido van Rossum <guido@python.org> wrote:
And I'm of the opinion that the most fundamental property of an enum is that it doesn't print as an int.
That part is easy. 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.
Making an appropriate str/repr just requires deciding what it should be. I chose to make the reprs give more information, but there's no reason they couldn't be simplified. I'll see if I can come up with a syntax for declaration that we're both happy with, but I doubt I'll be as happy with it as what I've currently got (despite its edge cases). Tim Delaney

On Feb 13, 2013, at 08:19 AM, Tim Delaney wrote:
See, I don't care about that much because I define an enum once, but I *use* it hundreds of times. Thus a little more verbosity/explicitness at definition time is a one-time cost, and a very low one at that. It might not seems that way when you're writing them in so many mailing list replies though ;). In real code, having a really good str and repr, along with good semantics makes them much more useful for debugging, and very pleasant to use. Cheers, -Barry

On 14 February 2013 05:19, Barry Warsaw <barry@python.org> wrote:
My main concern regarding this is ensuring that: 1. Values don't get repeated. 2. Values don't get skipped (unless explicitly skipped); #1 obviously is the most important property, but in practice I find #2 to be very important as well. Whilst it's more verbose, the ... syntax isn't bad, and I've added support to it to my enum to play with. Currently my enum implementation includes the kitchen sink, but I'm looking to do some renovations soon.
In real code, having a really good str and repr, along with good semantics makes them much more useful for debugging, and very pleasant to use.
Absolutely. But it's not just that either. If you can convert from the string name to the enum, it makes the enum suitable as a transport mechanism. Each end can have different values for the enum in practice so long as the names don't change - it can make things much more robust across versions. Tim Delaney

On Feb 14, 2013, at 06:28 AM, Tim Delaney wrote:
1. Values don't get repeated.
This is easy, and flufl.enum guarantees this, even for subclasses: >>> class Colors(Enum): ... red = 1 ... blue = 1 ... Traceback (most recent call last): ... TypeError: Multiple enum values: 1 >>> class Colors(Enum): ... red = 1 ... blue = 2 ... >>> class MoreColors(Colors): ... green = 2 ... Traceback (most recent call last): ... TypeError: Multiple enum values: 2
2. Values don't get skipped (unless explicitly skipped);
Is #2 is the reason why you have different subclasses for flag enums, since the definition of "skipped" is different? Many folks have said they don't care about the actual enum values, so for them, skips don't matter. Setting aside explicit skips, you can add such verification with a class decorator, e.g.: -----snip snip----- from flufl.enum import Enum def noskips(cls): for i, intval in enumerate(cls, start=1): if i != int(intval): raise TypeError('Skipped value: {}'.format(i)) return cls @noskips class GoodColors(Enum): green = 3 blue = 1 red = 2 @noskips class BadColors(Enum): green = 4 blue = 1 red = 2 $ python3 noskips.py Traceback (most recent call last): ... TypeError: Skipped value: 3 -----snip snip-----
If you can convert from the string name to the enum, it makes the enum suitable as a transport mechanism.
>>> from flufl.enum import make >>> Colors = make('Colors', 'red green blue'.split()) >>> Colors['red'] <EnumValue: Colors.red [int=1]> >>> Colors[2] <EnumValue: Colors.green [int=2]> >>> Colors[Colors.green.name] <EnumValue: Colors.green [int=2]> Cheers, -Barry

On Wed, 13 Feb 2013 13:19:14 -0500 Barry Warsaw <barry@python.org> wrote:
The cost is not low when you have many values. Also, with many values and one value per line, it can make your declaration very long vertically. And it's not always true that you use an enum much more often than you define it. For example, you may define many error codes (e.g. errnos) for compatibility with another system, but only check a few of them explicitly in your application code. Regards Antoine.

Le Wed, 13 Feb 2013 17:28:09 -0500, Barry Warsaw <barry@python.org> a écrit :
One of my common use cases for wanting an enum has always been to map a third-party protocol or library's error codes. Really, the one screaming use case in the stdlib is in the errno module :-) For that you have to be able to define an enum that subclasses int, though. Regards Antoine.

Forgive me, a lurker, but I wonder: how does an enum differ in usage from something like a Symbol from ruby, other than that an enum is required to be declared beforehand? I'm definitely a python guy, but the idea of identifiers that are their own value interests me, and seems like it could have some overlap with the concept and usage of enums. If there's not much difference in usage between enums and symbols apart from declaration, perhaps we could make the declaration optional and combine the concepts? This would certainly require parser changes. Ryan

I don't know Ruby symbols, but if they are anything like Lisp symbols, they are completely different. Lisp symbols are interned strings -- Python just uses strings (and if they happen to be interned you get a modest speedup). This is for things like: if hasattr(x, 'foo'): x.foo() On Tue, Feb 12, 2013 at 1:55 PM, <ryan@ryanhiebert.com> wrote:
-- --Guido van Rossum (python.org/~guido)

On 13 February 2013 09:29, Jonathan Slenders <jonathan@slenders.be> wrote:
That's my conclusion as well. To get a nice* enum with guaranteed semantics and no edge cases, I think it needs parser support. * for my definition of "nice". Tim Delaney

Just quickly thinking a little out of the box. Suppose, that we added grammar for scheme-like symbols. (You know, these atomic identifiers, prefixed with a quote. I don't know much about scheme, but as I understand, they are just constants.) In Python, the syntax could be similar, we could simply do in a global module scope: sym symbol which would translate to: locals()['symbol'] = Symbol('symbol') Where Symbol is a built-in that cannot change, which doesn't have a content, except for its name, and never equals anything but itself. From then on, we can access it through the name-binding 'symbol' Then we could probably create enums, using the classes as we have: class Colors: sym red sym green sym blue There are probably some gaps here in my reasoning, but this could also work. Cheers, Jonathan 2013/2/12 Tim Delaney <timothy.c.delaney@gmail.com>

Stefan Krah writes:
I'll take you at your word. But maybe you should stop reading now.<wink/>
Is a new enum keyword still a possibility? To me that seems to be the cleanest way.
If parser support were on the table, couldn't we avoid a new keyword by aping the Pascal syntax with class Color(Enum) = [RED, GREEN, BLUE] or something like that? I realize this is horrible because of the missing colon, and because availability of the syntax depends on the base class. Maybe it will suggest something to somebody, though. The base class seems to be needed to support variants like OrderedEnum and FlagEnum, as the implicit Enum class Color = [RED, GREEN, BLUE] can't disambiguate common use cases (ordered enums, or-able flags). Another possibility would be to allow the list of identifiers as the base class: class Color([RED, GREEN, BLUE]): pass I suppose this is better than the version using "=", which doesn't seem to allow explicit initialization of the enumerated ids, or operations on them, in the class body. Perhaps the Enum base class should be explicit: class Color(Enum, [RED, GREEN, BLUE]): pass In all cases the point of using lists instead of (implicit) tuples is to clearly guarantee order of evaluation to support OrderedEnum. Sets are out because FlagEnum doesn't need such support.

On 12 February 2013 19:29, Guido van Rossum <guido@python.org> wrote:
Just throwing in another syntax that is valid, requires no quotes and no magic - in case someone is interested in developing on it:
Colors = make(RED=..., GREEN=..., BLUE=...)
participants (13)
-
Antoine Pitrou
-
Barry Warsaw
-
Eli Bendersky
-
Greg Ewing
-
Guido van Rossum
-
Joao S. O. Bueno
-
Jonathan Slenders
-
Mark Lawrence
-
ryan@ryanhiebert.com
-
Stefan Krah
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Tim Delaney