Python's sad, unimaginative Enum
acooke.org
acooke.org
Syntactic nitpicking is bikeshedding. Remember, Guido makes choices that may not be obvious unless you're Dutch, but he has a pretty good track record overall.
A better critique would focus on whether the language actually needed an Enum construct. Most people get by most of the time without it. Python already provides many ways to do it (module variables, class variables, etc).
The main problem with Enum is that you won't be able to ignore it. Enums will start popping-up in many modules and packages, so it will have to become part of your core Python knowledge (things you have to teach to beginners so they can work with existing code).
Contrast that with named tuples. They can be ignored (i.e. you can treat them like regular tuples and you'll get by just fine). Also, named tuples are profoundly more useful (i.e. using them is one of the easiest ways to improve the clarity of your code).
I'm not the one you replied to, and it's tough to not sound sarcastic on the internet, but I'm honestly curious what the purpose of an Enum type is. I didn't really get their purpose when I took Java I, and I don't really see why Python needs them.
What is the advantage of declaring an Enum vs something like,
VALUE_ONE, VALUE_TWO, VALUE_THREE = range(3)
? >>> class MyEnum(Enum):
... VALUE_ONE, VALUE_TWO, VALUE_THREE = range(3)
...
>>> v = MyEnum.VALUE_ONE
>>> v == 0
False
>>> v == MyEnum.VALUE_ONE
True
>>> type(v)
<enum 'MyEnum'>
>>> isinstance(v, MyEnum)
True
>>> print(v)
MyEnum.VALUE_ONE
>>>
Now try to think how these would result if you'd defined VALUE_ONE... your way.If you knew that color was of the enum-type my_gui_lib.standard_colors you know exactly which colors you can use and you can reuse them for fill_ellipse also because it most likely uses the same type. Instead of every function having to reiterate all valid integer values of color or referring to some table. Your editor will also provide you with auto complete of valid values unless you're coding in notepad.
In a type safe language you will even get compilation error if you provide a type/value out of range but since python is dynamic this would be hard. It's simply impossible to pass the function an invalid value, which is invaluable. So in python the value added isn't a strong as in a strong language but it's still there.
This was discussed in the mailing list in the past, and I even had a prototype of CPython with constants replaced by IntEnum. Due to its being an actual int it retains backwards compatibility while providing very nice printable representation for the constants.
You and a couple other vociferous proponents completely drown-out the early commenters who valued language compactness and learnability over a pervasive new construct that solves a somewhat unimportant problem.
The fact that some large projects may have a use for Enums wasn't balanced against an impending avalanche of Enums being sprinkled in beginner code, small projects, recipes, etc. I expect the use of Enums will become pervasive simply because they're there, not because a given snippet of code actually needs it.
As a person who teaches Python to engineers, I dread adding yet another have-to-know construct to the cirriculum. Python is no longer a small language.
Also, there is a lot of machinery behind Enums (killing a mosquito with a cannon). So, we should expect that people will hack it, abuse it, twist it into knots, subclass it, invent tricks will it, develop funky idioms, etc. IMO, none of this is worth it. The "problem" wasn't that important to begin with.
That said, the discussion is moot. There is no need to defend the proposal any more. Guido has accepted the PEP and it will enter the language, for better or for worse.
If we had to have a Enum, the one that was accepted was a reasonable choice. For the most part, the discussion did a nice job of considering existing recipes, possible use cases, and being as Pythonic as possible.
My only criticism of the process is that handful of enthusiasts (Eli, Ethan, Barry, etc) launched an avalanche of implementation and api-choice emails that buried and ignored the posts suggesting the language would be better-off without Enum. AFAICT, there was no honest discussion of simply establishing best practices using existing tools.
Naturally I participated in the discussion, but the vast majority of emails I sent was to steer it long after the decision to add some kind of enumeration was made.
It depends. In our code we have constants integers used as kinds marker that get stored in database. Messing them would break things bad. And we have them everywhere. And we have maybe over a thousand of them.
Had we had this enum from the beginning we would now probably have a code base in a slightly but significantly better shape.
I read that and I thought, "that's such a thing Raymond Hettinger would say". Then I glanced up at the username :)
This isn't a new construct, or a syntax change, or an additional builtin. It's just an addition to the stdlib so that you can go:
from enum import Enum
...which isn't quite as earth-shattering as some people in this thread are making out. class Colour(Enum):
red
green
If Enum is a class, making a subclass should make a subclass like normal. Not change how code is parsed within the body of the class definition!And the same applies to subsequent imaginary suggestions.
If you want a syntax change then introduce a new 'enum' keyword to signal that we are dealing with a new syntax.
Similarly, if you want the statement 'red = 1' to result in Color.red == 'red', that is magic. It doesn't make sense. It should give you 1. The value you just assigned it.
I agree that passing a string to make a class (in the way of namedtuple) is ugly and undesirable and I would never use it.
But I don't think Mr. Cooke has made any suggestion that is better than "design by committee". Code which looks like Python, in a Python file, should behave like Python rather than throwing bizarre curveballs like implicitly turning 1 into "red" or a bitfield.
class EnumBodyDict(dict):
def __init__(self, *a, **kw):
self._keys_accessed = []
dict.__init__(self, *a, **kw)
def __getitem__(self, key):
self._keys_accessed.push(key)
return dict.__getitem__(self, *a, **kw)
class EnumMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
return EnumBodyDict()
def __new__(cls, name, bases, classdict):
next_enum_value = max(classdict.values()) + 1
for name in classdict._keys_accessed:
if name not in classdict:
classdict[name] = next_enum_value
next_enum_value += 1
return type.__new__(cls, name, bases, classdict)
class Enum(object, metaclass=EnumMeta):
# proposed enum implementation hereEvery language/framework/system has features some people won't like. Python has many too. But Python did a great job at striking a careful balance between expressibility and syntax, and most programmers who have to deal with it love it. Enum's design was guided by the same principles. Given that we didn't want to add new syntax to the language just for this feature (Python's minimal syntax is one of its greatest strengths), we had a few challenges to face. We tried to follow Python's overall guide of explicitness, while allowing numerous features.
The end goal? To replace the plethora of hand-cooked enums almost every large Python project has (including examples within Python itself).
Finally, remember that many issues here are issues of style and personal preference. It's not far from a brace style war really.
> Person = namedtuple('Person', ['name', 'age', 'gender'])
The string form is there to accomodate use cases where the field names are being cut-and-pasted from SQL or CSV headers or somesuch. Also, some people find it easier to type: > Person = namedtuple('Person', 'name, age, gender')That said, the preferred and recommended approach to define enums is with the class syntax.
data Suit = Spades
| Hearts
| Clubs
| Diamonds deriving (Show, Eq, Bounded, Enum)
this creates a type that acts exactly like an enum. With the added benefit of having a well-defined string representation and maxBound/minBound constants for free.ADTs and enums are meant to prevent this.
Smalltalk isn't about preventing things, it's about enabling them.
Ambiguous, because in the:
# y = 5
#
match x with:
1: "my string"
2|3: print "2,3"
y: print y
there is a problem. Because, really, in case of 'y' you want to be able to do both: bind variables; use already defined variables. And there is no way you can do both with clean and concise syntax. On the other hand, there is already a way to achieve the same results with if,elif,else. That's why (sadly) pattern matching is not there.Every language solves that with a desambiguation rule. In haskell, the first matching line runs. It's no worse than the way C, Java, and family solve the if-else ambiguity, for example.
|y -> y + x
If y was defined in the outside scope, the case scope just shadows it. y = 5
match x with:
1: print '1'
y: print '5'
he will think it is equivalent to: y = 5
if x == 1: print '1'
elif x == y: print '5'
And that would not be true.let y = 5 in
match x with 1 -> print "my string" | 2 | 3 -> print "2,3" | z when z = y -> print y
matching a value of variable:
|z when z = y -> print y
free matching with a bind: |y -> print y
matching several values: |2|3 -> print "2 or 3"
matching tuples: |2,3 -> print "tuple 2,3"
Now, if I'm trying to come up with such syntax for Python, I see immediate problems. For example, lets consider following syntax for matching several values: match x with:
1: print "my string"
2,3: print "2,3"
Would it be equivalent to matching a tuple (2,3) or matching one of the values 2 or 3? Ambiguous. So you need some other syntax. Let's try a few variations: match x with:
1: print "my string"
2:3: print "2 or 3" # plain ugly
2|3: print "2 or 3" # interferes with '|' op
in (2,3): print "2 or 3" # too complicated
x in (2,3): print "2 or 3" # elif was simpler
Or consider: match x with:
1: print "my string"
y: print y
Would that be matching a value of variable (|z when z = y -> print y), or free matching with a bind (|y -> print y)?Having experimented with that a bit, I have a feeling, that it's just impossible to make up 'match' syntax for Python that would fit into the language. And I think that's why it is not there. And why we don't even want match syntax there. Now, of course I'd be happy to change my opinion, if somebody would rise up to the challenge and come up with some syntax that fits.
ML (Ocaml, I should say, because that's my experience) is like a functional C. As a production language, I'd recommend it highly. It has excellent garbage collection. It generates blazingly fast native code.
I can't speak either way about Haskell's use in production. I'm sure that many people have made it work. I just don't know how hard it is in practice.
Haskell is lazy, for one, which makes it hard to reason about performance. You can have production memory leaks that are a bitch to debug. It also has a much more powerful, but also more complex type system. Explicit functors (Ocaml) are replaced by implicit type classes, which make the language more attractive (I'll grant that) but also make it easier to hang yourself by the monads.
Haskell's still a great language, and there are a million things that recommend it. However, I don't see it as occupying the same space as the ML family. The main similarity is that both use Hindley-Milner type inference, but there are a lot of differences, too.
Finally, Haskell is pure (except in the IO Monad, and a couple others) while Ocaml's not. You have stateful arrays and ref cells in Ocaml, and use them all the time when you're writing high-performance production code. In Haskell, any IO is to be done "in the IO monad" (which means, "in the context of evaluating an IO a", the latter being a thunk that does IO and returns an a.)
To be precise, it's type _inference_ of Damas-Hindley-Milner + Typeclasses that poses the most gotchas. (Plain vanilla DHM is just ML. You can hack Haskell without class, but you can't get the class out of Haskell.)
Experience with Prolog helps, since a form of logical deduction, generally speaking, is what's happening behind the scenes. You could think of the compiler as applying AI to figure out the types. This kind of AI is magic when it works and baffling when it barfs.
One way out is to skip the AI entirely and annotate all the types by hand. Now the compiler only has to check types, not infer them. When it stumbles over imperfect code, the error messages become a lot more decipherable.
Anyone who's used the object-oriented part of OCaml can tell you that inheritance can mix with Hindley-Milner awkwardly... but I'm not sure that really means algebraic types and pattern matching couldn't be integrated into Java, Python, or whatever somehow. For all I know, maybe it's already on the way into C++...
Colour = Enum('Colour', 'red, green')
There's a difference here between 'name' as in where in the namespace a value is listed, and 'name' as in what descriptive name something has. For instance, if you were to do this... Colour = Enum('Colour', 'red, green')
Tint = Colour
Would you want the values from Tint to suddenly say that they're different values from the values in Colour, even though it's the same underlying object? I doubt it.Similarly, regarding having to assign numbers: this makes a hell of a lot of sense any time you're trying to serialize things for interoperation between multiple services. There's a reason why protobufs do it this way as well: you get really powerful backwards-compatibility options, which you might not realize you need at first but become extremely handy down the line.
On Wed, Feb 27, 2013 at 2:03 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
> I'm beginning to see why enums as a class has not yet been added to Python.
> We don't want to complicate the language with too many choices, yet there is
> no One Obvious Enum to fit the wide variety of use-cases:
>
> - named int enums (http status codes)
> - named str enums (tkinter options)
> - named bitmask enums (file-type options)
> - named valueless enums (any random set of names)
> - named valueless-yet-orderable enums (any not-so-random set of names
That's probably the best succinct description of the core problem I've
seen, and I've been following the various enum-related dicussions for
years
Cheers,
Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, AustraliaBut in the context of a C-style language, I can understand the "what? these are enums. why do they have methods? and subclassing?"
It might be good if they had a different name.
I can fully understand that someone used to C enums would go WTF at seeing Java enums for the first time. What makes it narrow-minded is when this leads you to rejecting them outright and writing blog posts to ridicule them.
> What would you expect a Pythonic enum to look like?
>
> class Colour(Enum):
> red
> green
Hell no. I mean, even Go isn't that implicit, you still have to use the iota there. Anyway, this was considered, and you can see the reasons against it (standard explicit is better than implicit) here: http://www.python.org/dev/peps/pep-0435/#id38 > class Colour(Enum):
> red = 1
> green = 1
>
> Still, at least the mistake above would raise an error. Wouldn't it? Nope.
> That's a feature. If you mess up on the non-optional values then you get
> "aliases".
Aliases are a feature, like it or not. Having 100% unique enums would not actually be a good thing, seeing as how sometimes libraries support multiple names for the same enum value for backwards compatibility reasons.And if you do actually want to prevent the duplication problem, you can do this
class Color(Enum):
red, green, blue = range(3)
red_alias = red
Or alternatively, if having to specify how many values is too unimaginative for you, something like class Color(Enum):
red, green, blue, *_ = range(2**128)
red_alias = red
will also work. > So, you go hunting around in the docs to see if there's any way at all of
> avoiding the need to assign values manually. And there is:
>
> Colour = Enum('Colour', 'red, green')
>
> which suffers from the same problems as namedtuples:
> - you need to repeat the class name (in a string, which your IDE is
> unlikely to check)
> - the parameters are themselves in a string, which your IDE is
> unlikely to parse and provide in auto-complete (they can be separate
> strings, in a sequence, but that doesn't really help).
>
> Now if two potentially useful library classes are suffering from the same
> problems than isn't that a BIT OF A HINT to try make things better? Nope. It
> just shows how important it is to not be imaginative. Or something (crack).
It's true! IDEs don't deal well with meta-programming, and Python isn't Lisp! I don't love the namedtuple syntax particularly either, but what solution would you propose? There doesn't seem to be an easy way around it, afaict. > And it gets worse. What values do you think the above provides?
>
> Strings? That would makes sense (red = 'red'), in that it would display
> nicely and is going to provide easy to debug values. So nope.
Well, if we read the PEP (http://www.python.org/dev/peps/pep-0435/#id26): >>> class Shake(Enum):
... vanilla = 7
... chocolate = 4
... cookies = 9
... mint = 3
...
>>> for shake in Shake:
... print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint
We see that printing an enum value gives the string representation, so I don't really think that it not defaulting to strings is that much of a problem. > Integers from zero? I mean that's how Python counts indices and there's "only
> one way to do it" so that's how Python counts enums, right? Nope.
I think I almost agree with you here. That said, zero does have a specific meaning in a boolean context, so I can see why they didn't go for it. > OK, so bit fields? That way we can do cool Colour.red | Colour.green and
> make the C programmers feel at home? Nope.
Aside from the whole never going to happen argument, I think this kind of shows the problem with doing this implicitly; whatever you do, it's not going to work for some people. > Give up? I'll tell you. It counts from 1. Presumably because it's really
> common to use the "if not enum" idiom. In someone's crack-addled dreams.
Hey, don't despair so much. You can always read the PEP and realise you can actually provide a dictionary as the second argument. So maybe you'd prefer def MyEnum(name, vals):
return Enum(name, {val: i for i, val in enumerate(vals)})
to have your enums start from 0. I should mention, this information is in the PEP: http://www.python.org/dev/peps/pep-0435/#id35 class Colour(Enum):
* red
* green
* blue
* none, transparent, empty class Color(Enum):
red, green, blue, *_ = range(2**128)
red_alias = red
Does that create a list of size 2^128-3 = 340282366920938463463374607431768211453 and assign it to Color._? a, *b = range(10)
makes b into a list. (And I think the star syntax in assignments is only Python 3, as far as I remember Python 2 only has star in argument lists of functions.) >>> a, *b = range(2**128)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C ssize_t
Ha.No, Go just provides plain boring wannabe enums.
Pascal(1972), Modula-2(1978), Ada(1983), and many others:
type Color = (red, green)
ord(red) = 1, ord(green) = 2
len(Color) = 2
succ(red) = green
pred(green) = red
red < green
type colors = array [Color] of int;The Java enums can be as concise as the C/C++ one but since it's also a class, you can add all kinds of interesting stuff to decorate it such as constructors, getters, type converters, etc... Really a pleasure to work with.
They would be safer to work with if it was invalid to write a switch statement with a missing case and no default. I see a lot of code like:
switch (foo) { case Bar: ... case Baz: ... default: throw new IllegalArgumentException(); }
I will admit I kind of like Java's syntax for enums because it makes it very clear what exactly an enum is: a class with a bounded set of instances:
enum Month {
JANUARY(31, "Jan"),
FEBRUARY(28, "Feb"),
MARCH(31, "Mar"),
...;
Month(int days, String abbreviation) { ... }
}
(But honestly, Java enums have a number of annoyances and feel very "tacked on" like generics do.) enum Color: red, green, blue
Right now enumerations are a mess. It seems like every project does them slightly differently, and the new enum library just adds a new way to do them slightly differently. That's like the opposite of PEP 8.http://stackoverflow.com/questions/630803/associating-enums-...
The new enum library does more without introducing new syntax, and I think that's a good thing.
class Colour(Enum):
red, green, blue = range(3) Colour = Enum('Colour', 'red, green')
That is beyond sad.http://stackoverflow.com/a/1695250
Even with reverse mapping.
You'd get heavily downvoted for asking "Did Peter Norvig not read the fucking article?", yet that's exactly what you've put into everyone's mind with your comment.