9 ms·
Python's sad, unimaginative Enum
- Vivtek 13y agoBest first two comments ever, though.
- batterseapower 13y agoAll enum types look a bit sad when you're used to the full algebraic sum types + pattern matching provided by a functional language
- frou_dh 13y agoEven as someone who hasn't done any real work in such languages, your statement strikes me as viscerally true. Those features are an obvious boon for reliability.
- tomp 13y agoBut sometimes you only need enums (which are, arguably, a subcategory of ADTs). E.g. playing card suits, true/false, ...
- tikhonj 13y agoNot just arguably--that's exactly what they are. E.g., in Haskell: 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.
- gnaritas 13y agoTrue and false work fine as singleton subclasses of the abstract class Boolean; see Smalltalk.
- dchichkov 13y agoThere is a reason why pattern matching syntactic sugar is not in python. Pattern matching is very powerful, but it is also ambiguous. And it is impossible to come up with good looking syntax, unfortunately. 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.
- jedi_stannis 13y agoThis problem is also present in Haskell and it seems to work out pretty well there
- marcosdumay 13y agoThat. 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.
- Tobu 13y agoI don't get the issue. In OCaml you can do |y -> y + x If y was defined in the outside scope, the case scope just shadows it.
- dchichkov 13y agoYes, if you would only allow binding it resolves the conflict. But, if a five year old would look at the code: 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.
- a-nikolaev 13y agoI think, he wants to match the case when x == y. You can do that too. let y = 5 in match x with 1 -> print "my string" | 2 | 3 -> print "2,3" | z when z = y -> print y
- MostAwesomeDude 13y agoDon't say "a functional language" when you mean "a member of the ML family, like OCaml or Haskell."
- chowells 13y agoHaskell is not an ML. Not even close.
- spacemanaki 13y agoCould you elaborate on this? I would agree with "Haskell is not an ML" but I think the MLs (SML, OCaml, Mythryl, F#) are close to Haskell, and closer to Haskell than to other programming languages, certainly closer than to Scala. Haskell is lazy, pure, and has typeclasses, while MLs are strict, impure, and (sometimes, usually) have modules, ... while there might be a lot of other differences, there are a lot of similarities too, between algebraic datatypes, Hindley-Milner, pattern matching, etc...
- michaelochurch 13y agoNot grandparent, but let me try. 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.)
- tome 13y agoRight, this is why I eventually, and sadly, left Python behind.
- andreasvc 13y agoIs there a technical reason why algebraic types and pattern matching are only found in functional languages, or is it an accident of history?
- tsuyoshi 13y agoIt's sort of an accident: the inventor of ML (which SML, OCaml, and F# are descended from, and Haskell has liberally borrowed from) is the "Milner" in Hindley-Milner type checking, and algebraic types are one of the things that make Hindley-Milner useful. 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++...
- ansgri 13y agoDon't say "a functional language" when you mean Strongly Typed languages with a specific type system. I.e. not Clojure or Scheme.
- michaelochurch 13y agoYou can write your own pattern-matching and enum/union types in Clojure. It'll be a couple days of work, but it can be done.
- dlitz 13y agoPresumably, you should also be able to do something like this: class Colour(Enum): red, green, blue = range(3)
- sn6uv 13y agorange(1,4)* because it counts from 1 (range counts from 0).
- Tobu 13y agoStarting from zero solves the "one right way to do it" problem which is mentioned in the blog post.
- marcosdumay 13y agoYes, but recreates the "a value must be true in a boolean context" problem discussed in the PIP.
- pekk 13y agoCooke's suggestion is pure special-case magic: 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.
- suprfsat 13y agoYou can get that syntax by using a metaclass. It's not that hard.
- JeremyBanks 13y agoI don't agree with his suggestion, but it wouldn't require a change to the parser. You can already pull that off using metaclasses. Something like this (untested): 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 here
- eliben 13y agoYou're right, this can be done without changing the Python syntax and in fact there was a proposal (with implementation) on the table for a long time. But eventually it was deemed to be too error prone and the more explicit approach was chosen. Python often resolved a tradeoff in favor of explicitness, and IMHO this ended up being a good decision.
- QuixoticChris 13y agoColour = Enum('Colour', 'red, green') That is beyond sad.
- Demiurge 13y agoYes, it's an emotional Enum according to emotional programmers who have strong feelings about the matter!
- lclarkmichalek 13y ago> 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 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 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 http://www.python.org/dev/peps/pep-0435/#id35
- aiiane 13y agoI actually disagree with basically all of the points raised in this article. Sure, apparently Enum doesn't fit your particular desires for it - but there's good reasons why those choices were made. 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.
- TheSilentMan 13y agoI think the argument about specifying values is that it should be optional, not that it shouldn't exist.
- kemayo 13y agoWell, it is optional, in the sense that there's a provided syntax which doesn't require it. (The namedtuple-esque one.)
- elehack 13y agoMildly unrelated, but when I think of the opposite ("imaginative" enums), the first thing that comes to mind is this amusing vignette on the design process of Java's enums: http://blog.des.no/2009/01/no-silly-its-enum/ http://blog.des.no/2009/01/no-silly-its-enum/
- brazzy 13y agoHow embarrassingly narrow-minded. Yeah, Java's enums are fully fledged classes. Which actually makes them extremely powerful and useful, more useful than enums in any other language. I've seen more than one C programmer unable to cope with such an extreme deviation from their bunch-of-integer-constants expectation.
- elehack 13y agoYeah, Java's enums are very powerful and useful. But 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.
- brazzy 13y agoThe essential property (a type having a fixed number of named instances) is still there, so the name is fine. 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.
- samps 13y agoOP: if you want bitmask-style enums, that's easy to code up with a derived class. The reference implementation shows how: https://bitbucket.org/stoneleaf/aenum/src/0c66e1a5ec7b76388a3bab5403fdcf4729b9802c/aenum.py?at=default#cl-420 https://bitbucket.org/stoneleaf/aenum/src/0c66e1a5ec7b76388a...
- Tobu 13y agoLovely!
- d0mine 13y agoBelow is the excerpt from the module's docstring: 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, Australia
- NelsonMinar 13y ago"only real achievement is not doing anything new". That sounds like an excellent endorsement for a new Python language feature. Leave the crazy syntax innovation for hipster languages with unreadable, poorly thought out features. I've got work to do in Python.
- overgard 13y agoI never got python's lack of built in enum syntax. I get not wanting to add unnecessary keywords, but unless you're doing something like Scheme or Smalltalk where you're going for almost no syntax at all, you would think it would pay off to put in keywords for things that literally occur in every program. I feel like I should just be able to write this: 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.
- kemayo 13y agoWell, any new enum syntax would be "a new way to do them slightly differently". This one just has the advantage of being in the standard library, and thus is presumably what new usage will gravitate towards.
- j-kidd 13y agoIf we go with your way, then we would have problem like this: http://stackoverflow.com/questions/630803/associating-enums-with-strings-in-c-sharp 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.
- raymondh 13y agoOne minor correction about named tuples. The syntax for field names can be an iterable or a string. The preferred way to do it is: > 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')
- eliben 13y agoAll the same ways were chosen for the Enum functional API, by the way (list, dict, string separated with either spaces or commas). In general, the functional API was inspired mostly by namedtuple. It's not that the namedtuple syntax is pretty. It's not, we all know that, but that's what Python can offer at this point. But it's good enough so people learn to live with it, get used to it and intuitively understand what it means, and that's good enough. That said, the preferred and recommended approach to define enums is with the class syntax.
- badgar 13y agoWhat's up with Peter Norvig's comment at the bottom? Did he not RTFA? He suggests a solution that the article criticizes heavily halfway through.
- cma 13y agoWeird that he didn't address it, but optimizing for IDEs is not python's style. Although I'm sure the pains they went through with the 2to3 tool might make some of them second guess it.
- kami8845 13y agoWhy is it OK to be rude through abbreviations? 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.
- philwelch 13y agoInitialisms like "RTFA" lose their literal intensity through overuse.
- icebraining 13y agoWell, to me RTFA is such a common expression that I don't even associate it with the original words anymore; it has become a word on its own.
- raymondh 13y agoI think the author is sad for the wrong reason. 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).
- digisign 13y agoHere, here. Been trying to come up with a reason to use these over named tuples but they are few.
- eliben 13y agoIf you can't distinguish between enumerations and named tuples, I think you have a bigger problem than you think. I mean this in full seriousness. Conceptually, the two constructs have completely different uses. That you can twist and bend one to implement a faint shadow of the other doesn't mean they are the same.
- goostavos 13y ago>Conceptually, the two constructs have completely different uses. 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) ?
- btipling 13y agoA shallow nitpicking of language syntax. I would rather read about how a programming language fares at solving difficult problems. Not a minor quibble that has no impact on anything other than this programmer's sensibilities. It's the kind of thing that makes me think people like this are a type, a type that want to abandon semicolons and jump on languages that look better but perform worse.
- tikhonj 13y agoAbandoning semi-colons but having worse performance? Sounds like Python.
- ansgri 13y agoSemicolons are a triple cognitive overhead (errors from omitting them, irritation that they add no value in well-formatted code, and ability to write very poorly formatted code), where their absence has the only overhead of unnatural ways of breaking long lines, so typical programmer performance should be better.
- Tmmrn 13y agoSomeone on stackoverflow posted a nice hacky way: http://stackoverflow.com/a/1695250 http://stackoverflow.com/a/1695250 Even with reverse mapping.
- eliben 13y agoEvery point raised by this post was discussed and rehashed multiple times, debated, argued over, complete with emotions. The end result was something that feels right to the vast majority of the discussion participants (give or take a few minor details.) Anyone has the right to criticize, and anyone also has the right to participate in the design and decisions - this is open source. Sadly way more people choose to take advantage of the former freedom than of the the latter. But that's old news, just the way things are. Every 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.
- laureny 13y agoAs much as I have qualms about Java, if there is one thing that it perfectly nailed, it's enums. 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.
- bhickey 13y agoI have a single gripe with how Java handles enums. 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(); }
- Too 13y agoI don't know the state of lint-tools for java but for C and C# most lint-tools do this check.
- nayefc 13y agoNow I appreciate Ruby symbols 10x more.
- nilved 13y agoI've still yet to find a good use case for Enum. It's (lousy, inconsistent) syntactic sugar that none of my code will use.
- smegel 13y agoA stdlib implementation is a whole lot easier and less disruptive than new syntax in core. This counts for a lot. But of course you're vastly more limited in what you can do. The main benefit of this proposal that i can see is not the specific functionality it provides, but simply the fact that it makes it clear and explicit that this is a set of constants that have some meaning to the module - putting such constants in ordinary classes/namespaces (along with everything else) can obscure the fact they are not variables/attributes.
- jrockway 13y agoI personally use enums more than I declare them, so I don't mind a little bit of namedtuple-like repetitive syntax. I'm already used to namedtuple, so I don't see anything wrong with reusing that syntax for enum. It's Python, not a brand-new programming language. 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.)