14 captures
22 Jun 2013 - 13 Jan 2024
May JUN Jul
22
2012 2013 2014
success
fail

About this capture

COLLECTED BY

Organization: Alexa Crawls

Starting in 1996, Alexa Internet has been donating their crawl data to the Internet Archive. Flowing in every day, these data are added to the Wayback Machine after an embargo period.

Collection: Alexa Crawls

Starting in 1996, Alexa Internet has been donating their crawl data to the Internet Archive. Flowing in every day, these data are added to the Wayback Machine after an embargo period.
TIMESTAMPS

The Wayback Machine - http://web.archive.org/web/20130622074048/http://lwn.net:80/Articles/551242/
 
LWN.net Logo

Log in now

Create an account

Subscribe to LWN

Return to the Development page

LWN.net Weekly Edition for June 20, 2013

Pencil, Pencil, and Pencil

Dividing the Linux desktop

LWN.net Weekly Edition for June 13, 2013

A report from pgCon 2013

An "enum" for Python 3

ByJake Edge
May 22, 2013

Designing an enumeration type (i.e. "enum") for a language may seem like a straightforward exercise, but the recently "completed" discussions over Python's PEP 435 show that it has a few wrinkles. The discussion spanned several long threads in two mailing lists (python-ideas, python-devel) going back to January in this particular iteration, but the idea is far older than that. A different approach was suggested in PEP 354, which was proposed in 2005 but rejected at that time, largely due to lack of widespread interest. A 2010 discussion also led nowhere (at least in terms of the standard library), but the most recent discussions finally bore fruit: Guido van Rossum accepted PEP 435 on May 9.

The basic idea is to have a class that implements an enum, which, in Python, might look a lot like:

    from enum import Enum

    class Color(Enum):
        red = 1
        green = 2
        blue = 3
That would allow using Color.green (and the others) as a constant, effectively. Not only would Color.blue have a value, but it would also have a name ('blue') and an order (based on the declaration order). Enums can also be iterated over, so that:
    for color in Color:
        print(color, color.name, color.value)
gives:
    Color.red red 1
    Color.green green 2
    Color.blue blue 3

Along the way, there were several different enum proposals made. Ethan Furman offered one that incorporated multiple types of enum, including ones for bit flags, string-valued enums, and automatically numbered sequences. Alex Stewart came up with a different syntax for defining enums to avoid the requirement to specify each numeric value. Neither made it to the PEP stage, though pieces of both were adopted into the first draft of PEP 435, which was authored by Eli Bendersky and Barry Warsaw.

There are a couple of fairly obvious motivations for adding enums, which were laid out in the PEP. An immutable set of related, constant values is a useful construct. Making them their own type, rather than just using sequences of some other basic type (like integer or string) means that error checking can be done (i.e. no duplicates) and that nonsensical operations can raise errors (e.g. Color.blue * 42). Finally, it is convenient to be able to declare enum members once but to still be able to get a string representation of the member name (i.e. without some kind of overt assignment like: green.name='green').

Some of the use cases mentioned early in the discussion of the PEP are for values like stdin and stdout, the flags for socket()orseek() calls, HTTP error codes, opcodes from the dis (Python bytecode disassembly) module, and so forth. One of the questions that was immediately raised about the original version of the PEP was its insistence that "Enums are not integers!", so ordered comparisons like:

    Color.red < Color.green
would raise an exception, though equality tests would not:
    print(Color.green == 2)
    True
To some, that seemed to run directly counter to the whole idea of an enum type, but allowing ordered comparisons has some unexpected consequences as Warsaw described. Two different enums could be compared with potentially nonsensical results:
    print(MyAnimal.cat == YourAnimal.dog)
    True
In general, the belief is that "named integers" is a small subset of the use cases for enums, and that most uses do not need ordered comparisons. But, the final accepted PEP does have an IntEnum variant that provides the ordering desired by some. IntEnum members are also a subclass of int, so they can be used to replace user-facing constants in the standard library that are already treated as integers (e.g. HTTP error codes, socket() and seek() flags, etc.).

Asecond revision of the PEP was posted in April, after lengthy discussion both in python-devel and python-ideas. Furman offered up another proposal, this time as an unnumbered PEP with four separate classes for different types of enums. Two different views of enums arose in the discussion, as Furman summarized:

There seems to be two basic camps: those that think an enum should be valueless, and have nothing to do with an integer besides using it to select the appropriate enumerator [...] and those for whom the integer is an integral part of the enumeration, whether for sorting, comparing, selecting an index, or whatever.

The critical aspect of using or not using an integer as the base type is: what happens when an enumerator from one class is compared to an enumerator from another class? If the base type is int and they both have the same value, they'll be equal -- so much for type safety; if the base type is object, they won't be equal, but then you lose your easy to use int aspect, your sorting, etc.

Worse, if you have the base type be an int, but check for enumeration membership such that Color.red == 1 == Fruit.apple, but Color.red != Fruit.apple, you open a big can of worms because you just broke equality transitivity (or whatever it's called). We don't want that.

Furman's proposal looked overly complex to Bendersky and others commenting on a fairly short python-ideas thread. Meanwhile in python-devel, another monster thread was spinning up. The first objection to the revised PEP was in raising a NotImplementedError when doing ordered comparisons of enum members. That was quickly dispatched with a recognition that TypeError made far more sense. Other issues, such as the ordered comparison issue that was handled with IntEnum in the final version, did not resolve quite as quickly.

One question, originally raised by Antoine Pitrou, concerned the type of the enum members. The early PEP revisions considered Color.red to not be an instance of the Color class, and Warsaw strongly defended that view. At some level, that makes sense (since the members are actually attributes of the class), but it is confusing in other ways. In a sub-thread, Van Rossum, Warsaw, and others looked at the pros and cons of the types of enum members, as well as implementation details of various options. In the end, Van Rossum made some pronouncements on various features, including the question of member type, so:

    isinstance(Color.blue, Color)
    True
is now an official part of the specification.

As Python's benevolent dictator for life (BDFL), which is Van Rossum's only-semi-joking title, he can put an end to arguments and/or "bikeshedding" about language features. In the same thread, he made some further pronouncements (along with a plea for a halt to the bikeshedding). It is a privilege that he exercises infrequently, but it is clearly useful to the project to have someone in that role. Much like Linus Torvalds for the kernel, it can be quite helpful to have someone who can stop a seemingly endless thread.

Van Rossum's edicts came after Furman summarized the outstanding issues (after a summary request from Georg Brandl). That is a fairly common occurrence in long-running Python threads: someone will try to boil down the differences into a concise list of outstanding issues. Another nice feature of Python discussions is their tone, which is generally respectful and flame-free. Participants certainly disagree, sometimes strenuously, but the tone is refreshingly different from many other projects' mailing lists.

Not everyone is happy with the end result for enums, however. Andrew Cooke is particularly sad about the outcome. He points out that several expected behaviors for enums are not present in PEP 435:

    class Color(Enum):
        red = 1
        green = 1
is not an error; Color.green is an alias for Color.red (a dubious "feature", he noted with a bit of venom). In addition, there is a way to avoid having to assign values for each enum member (auto-numbering, essentially), but its syntax is clunky:
    Color = Enum('Color', 'red green blue')
Beyond having to repeat the class name as a string (which violates the "don't repeat yourself" (DRY) principle), it starts the numbering from one, rather than zero. Nick Coghlan responded to Cooke's complaints by more or less agreeing with the criticism. There is still room for improvement in Python enums, but PEP 435 represents a solid step forward, according to Coghlan.

It is instructive to watch the design of a language feature play out in public as they do for Python (and other languages). Enums are something that the developers will have to live with for a long time, so it is not surprising that there would be lots of participation and examination of the feature from many different angles. While PEP 435 probably didn't completely satisfy anyone's full set of requirements, there is still room for more features, both in the standard library and elsewhere, as Coghlan pointed out. The story of enums in Python likely does not end here.


(Log in to post comments)

An "enum" for Python 3

Posted May 22, 2013 19:16 UTC (Wed) by mathstuf (subscriber, #69389) [Link]

> print Color.green == 2
> True

Is there now a __past__ module in Python 3 that I missed?

An "enum" for Python 3

Posted May 22, 2013 20:03 UTC (Wed) by jake (editor, #205) [Link]

> Is there now a __past__ module in Python 3 that I missed?

Heh, no, sorry. I did my testing on Python 2.7 using the flufl.enum package that was used to prototype some of the ideas from the PEP. It's hard retraining my fingers (let alone my brain) on things like print.

print(Color.green == 2)
True

is that better?

jake

An "enum" for Python 3

Posted May 22, 2013 21:02 UTC (Wed) by mathstuf (subscriber, #69389) [Link]

Yeah, thanks :) . Of course, if you're at the REPL, no 'print' is needed.

> It's hard retraining my fingers (let alone my brain) on things like print.

I've gotten into the habit of using print as a "function" all the time since expressions in parentheses without commas is a no-op (you lose redirection support in both, that's usually fixed with output.write()).

An "enum" for Python 3

Posted May 24, 2013 7:46 UTC (Fri) by mgedmin (subscriber, #34497) [Link]

Heh. I got into the habit of using print("one value only") to be compatible with Python 2 and 3 at once, without a __future__ import. And then I discovered that print() is also valid in this mode, but does something completely different on Python 2. ;-)

All my blank lines became

()

An "enum" for Python 3

Posted May 22, 2013 23:35 UTC (Wed) by marcH (subscriber, #57642) [Link]

> In addition, there is a way to avoid having to assign values for each enum member (auto-numbering, essentially), but its syntax is clunky.

Wait... so enums are not integers, yet it's difficult not to give them each a number!? I must have missed something.

An "enum" for Python 3

Posted May 23, 2013 5:14 UTC (Thu) by gdt (subscriber, #6284) [Link]

Enums are numbered, but are not integers. Numbers need not support the complete set of arithmetic operations.

As a everyday example of this, counting numbers (1, 2, 3, ...) are incomplete for the subtraction operator. Try to represent 1 - 2 as a counting number.

Some of the questions in the mailing list were around what arithmetic properties a enum has: for example, are they ordered (ie, red < green). Since maths doesn't care for enums much (and so doesn't define them with rigor), the real question was what properties should a enum have to be useful for programming. For example, it was widely felt that (red + green) should not evaluate as it indicates a programming error.

Enums might be implemented using integers. But one can say the same for sets. The whole point of a programming language is to develop useful abstractions to simplify the programming task, otherwise it may as well be assembler.

An "enum" for Python 3

Posted May 23, 2013 7:31 UTC (Thu) by micka (subscriber, #38720) [Link]

Yet I don't understand why they must still be numbered.
That they are numbered as an implementation detail doesn't shock me, but should the numbered aspect be present in the language, event in the most common way to define them ?

An "enum" for Python 3

Posted May 23, 2013 8:16 UTC (Thu) by jezuch (subscriber, #52988) [Link]

> Yet I don't understand why they must still be numbered.

My thought as well. At this point it looks like it's a calque of enums from C (i would say "mindless" but it *was* discussed to death, after all ;) ). Having lived with enums in Java (which I find, frankly, rather excellent), I think it's a huge lost opportunity.

An "enum" for Python 3

Posted May 23, 2013 16:06 UTC (Thu) by pboddie (subscriber, #50784) [Link]

I don't track Java development actively, so it is nice to see that the original way of declaring enumerations has been deprecated. It was one of the ways that "classic Java", in contrast to the initial and sustained hype around the language, was let down by a number of unjustifiably weak features: a bit like taking a tour of an alien spacecraft from the future and finding out that the on-board sanitation is a bucket by the captain's seat.

The problem with introducing something similar in Python and having it as elegant as one might wish for is the unfortunate aversion amongst the core developers to changing the language syntax, even if that is really the best or most logical path to take in order to be able to express the new feature. So we end up with the risk that there is a shoe-horning of features into existing constructs that will probably serve to unnecessarily confuse people in future.

I suppose that one might look at the way it has been done and to consider more general mechanisms. In effect, values in PEP 435 enumerations can be regarded as predefined instances of the enumeration class, which I think is not always logical, since a colour is not an instance of a set of colours but rather a member of that set, although such a distinction is not always informative (integers are instances of the int type which can more or less be considered as the set of all integers even though it isn't really such a thing). However, this does allow us to consider using a similar shorthand elsewhere to define restricted collections of instances for a class.

What was noted in Andrew Cooke's response to the PEP is that the issue of introducing atoms/symbols in Python has been avoided once again. This would have been a particularly appropriate occasion to consider that matter.

An "enum" for Python 3

Posted May 23, 2013 8:33 UTC (Thu) by nix (subscriber, #2304) [Link]

Because most uses for enums eventually require that they be converted to a number for communication to some external thing which is representing them as (say) a bunch of C #defines, or, indeed, a C enum (or indeed any of a number of things in the existing standard library representing numbered things like error codes out there on the Internet). You could make enums an intrinsically unnumbered type, or a type whose numbering is hidden and could not be specified at all, but it would drastically limit their usefulness, since they would no longer be interconvertible with any other language's 'enum', and you'd need to write conversion wrappers all over the place.

An "enum" for Python 3

Posted May 24, 2013 7:49 UTC (Fri) by jezuch (subscriber, #52988) [Link]

> You could make enums an intrinsically unnumbered type, or a type whose numbering is hidden and could not be specified at all, but it would drastically limit their usefulness

Or you could allow enums to have members - fields and methods - and provide the conversion yourself when needed. You could also add some syntactic sugar for the common case. That's one of the great things about enums in Java - they are almost-regular classes, so they can engage in (interface) inheritance and polymorphism at will. Oh, and you can switch() on them ;)

An "enum" for Python 3

Posted May 23, 2013 8:34 UTC (Thu) by nix (subscriber, #2304) [Link]

Also... it's in the name. Enumerated types, are, well, enumerated. That means they are like, well, numbers. If they weren't like numbers it would be pretty silly to call them enumerated. :)

An "enum" for Python 3

Posted May 23, 2013 8:58 UTC (Thu) by micka (subscriber, #38720) [Link]

Ah, I admit I forgot about the etymology of the word enumeration.
Actually, for me, enumerate is more "give all elements one after the other" without any idea of counting or order, as it is in my native language (french), so that could have influenced the way I define the notion of enum.

An "enum" for Python 3

Posted May 23, 2013 9:37 UTC (Thu) by nix (subscriber, #2304) [Link]

Yeah, the link between 'enumerate' and 'number' is all the way back in Latin ("count out"). The fossil is still there in the word clearly enough for a native speaker to notice, though of course etymology is not destiny and there are *lots* of words whose etymology is a totally misleading guide to their meaning.

An "enum" for Python 3

Posted May 23, 2013 10:31 UTC (Thu) by ballombe (subscriber, #9523) [Link]

See <http://en.wikipedia.org/wiki/Ordinal_arithmetic> for one of the mathematical views on the subject.

An "enum" for Python 3

Posted May 24, 2013 22:36 UTC (Fri) by flewellyn (subscriber, #5047) [Link]

Why do we want enums, instead of symbols?

Perhaps being able to create a type which is a "symbol class", where each of its members is a symbolic constant that evaluates to itself, would solve the issue? Particularly if two symbols only compare equal if they are literally the same, i.e., the same symbol class member.

For instance:

class Color(Symbol)
red
green
blue

And then this would work:

isinstance(Color.blue, Color)
=> True

And this (for some pixel object):

pixel.setColor(Color.blue)

pixel.getColor() == Color.blue
=> True

But if you had this:

class Animal(Symbol)
cat
dog
elephant

Color.green == Animal.dog
=> False

Seriously. Enums are a hack for languages that don't have symbol types. Why not do things RIGHT?

An "enum" for Python 3

Posted May 25, 2013 10:34 UTC (Sat) by renox (subscriber, #23785) [Link]

>Seriously. Enums are a hack for languages that don't have symbol types. Why not do things RIGHT?

Except that very quickly you'll find you need to serialize your symbol and with serialization to an integer, you'll have reinvented Enums..

Why not keep it simple

Posted May 26, 2013 8:20 UTC (Sun) by man_ls (subscriber, #15091) [Link]

You can serialize to the symbol itself. E.g.: Color.blue can be serialized to the string "Color.blue". That is why symbols are defined using strings. Yes, it is a bit more wasteful than abbreviating to an integer, but also much, much clearer.

Functional languages

Posted May 25, 2013 15:53 UTC (Sat) by rwmj (guest, #5474) [Link]

Actually, enums are a hack for people that have never heard of constructed types and patterns. For example in OCaml:

type color = Black | White | Color of r * g * b
and r = float and g = float and b = float

let string_of_color = function
| Black -> "black"
| White -> "white"
| Color (1.0, 0.0, 0.0) -> "red"
| Color (r, g, b) when r = g && g = b -> "grey"
| Color (r, g, b) -> sprintf "(%g, %g, %g)" r g b

Functional languages

Posted May 26, 2013 4:51 UTC (Sun) by josh (subscriber, #17465) [Link]

Yeah, types like that are often the biggest thing I wish for when I'm in a lower-level language.

Functional languages

Posted May 26, 2013 8:55 UTC (Sun) by renox (subscriber, #23785) [Link]

> Black -> "black"

Bleah, are you seriously suggesting this code, which violate the DRY principle, should be taken as an example?

Functional languages

Posted May 26, 2013 9:08 UTC (Sun) by rwmj (guest, #5474) [Link]

If we're cherry picking single lines from the example code, let's pick another one:

| Color (r, g, b) when r = g && g = b -> "grey"

If you want to see some more serious use of matching: https://code.google.com/p/bitstring/

Functional languages

Posted May 27, 2013 15:28 UTC (Mon) by bjartur (subscriber, #67801) [Link]

The string representation is likely to change. The symbol should preferably not be renamed everytime.
Black -> "svartur"
or
Black -> "noir"

Copyright © 2013, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds