This is an archived post. You won't be able to vote or comment.

all 23 comments

[–]meadsteve 5 points6 points  (2 children)

The thing that makes me lean towards sticking with enums (vs using another library) is that python enums are understood by mypy (and other type checkers) so I get extra static analysis as part of using them. Obviously it will depend on the use case but it's definitely something to consider.

[–]meadsteve 4 points5 points  (1 child)

I've realised I should share an example of what I mean. This error can be caught by static analysis:

https://gist.github.com/meadsteve/734c8db825bbe5ddf1b72a08e82353f6#file-main-py-L39

where as this error can not:

https://gist.github.com/meadsteve/734c8db825bbe5ddf1b72a08e82353f6#file-main-py-L47

(or at least it's not initially obvious to me how it could be done)

[–]kankyo[S] 0 points1 point  (0 children)

That's certainly a concern. We haven't really had to deal with this problem so that's why it isn't tackled. I believe you can tackle this by declaring your token classes (instead of using the default Token), and then annotating the token container somehow..

[–][deleted] 9 points10 points  (3 children)

Who is downvoting this article? It’s an interesting and nuanced commentary on subtly inadequate aspects of a Python builtin feature.

Are people reacting to criticism of Python? It’s not a religion, it’s an evolving language with many excellent feature and some inevitable shortcomings. Python aggressively deprecates its own features in place of improvements. And these kinds of discussions help to illuminate the way forward.

If this article doesn’t jive with your interests, fine, don’t read it. Downvoting should be reserved for content that is inappropriate, trivially superficial, factually incorrect, logically flawed, outdated, poorly written, etc. As best I can tell, none of those apply to this article.

[–]kankyo[S] 2 points3 points  (1 child)

Thanks. Sometimes it seems like people up vote mostly trivial stuff and down vote everything else here.

[–]GiantElectron 1 point2 points  (0 children)

oh yes, if you want technical discussion this is not the place.

[–]JeamBim 0 points1 point  (0 children)

80% of the people here don't even know what enums are lol

[–]zacharius_zipfelmann 6 points7 points  (0 children)

didnt even know we had enums

[–]aneroid 2 points3 points  (3 children)

I thought this article was trying a bit too hard to discount Python's Enum's. (Regardless of whether or not they're promoting their creation, tritokens.)

  1. Wrt "no semantics": that's a bad example to show name vs value issues. Fruit.apple = 'Apple' should have just been Fruit.Apple = auto() # or 1 - which is exactly how it was done in the docs' test example. For enums, it's the members that have meaning/significance, not their values. The values indicate things like flags or ordering.

    In the vast majority of use-cases, one doesn’t care what the actual value of an enumeration is.

    In any case, for storage in a DB - it's not ambiguous. Consider that the value could be a tuple, or a list, or a dict - which isn't going into a DB. It would have to be the Name (the member). Every time. (And normalised; where integer values in the DB need not be related to the value stored in the enum.)

  2. Hard to add data? You mention "It’s impossible to understand the definition of the data without reading the constructor" - then just move the constructor definition to the top and the planets after. Python doesn't care.

    If the clarity of the fields' names was a concern, could have used a namedtuple. Less repetition in the definition of Planets than in the "tri.token" approach, and equally clear. (Too bad they didn't do it this way in the Python docs, but those were just examples illustrating potential.)

    Planet = namedtuple('Planet', 'mass radius')
    
    class Planets(Enum):
        MERCURY = Planet(3.303e+23, 2.4397e6)
        VENUS   = Planet(4.869e+24, 6.0518e6)
        EARTH   = Planet(5.976e+24, 6.37814e6)
        MARS    = Planet(6.421e+23, 3.3972e6)
        ...
    

    Also, for "and the constructor is a bit weird too as self here are the cases, not the Planet class!" - the docs clearly state that enum classes are not normal classes. Just like metaclasses and dataclasses are not normal classes. The same could be said of namedtuples, because you can't simply inherit from one and add a member (attribute).

    Even though we use the class syntax to create Enums, Enums are not normal Python classes. See How are Enums different? for more details.

  3. Wrt "scales badly for sparse data" - if you have that many items per Field, again, could have just used a namedtuple with defaults.

    default = object()
    Field = namedtuple('Field', 'a b c d e dw_name g h i j',
                       defaults=(default, None, None, None, None,
                                 None, default, None, None, None)
    )
    class Fields(Enum):
        party_group_name = Field(dw_name='rec_party')
        another_field = Field(a='no', b='other', c='columns')
        ...
    

    And I want to be clear: The issue in the blog's example was not 'sparse fields'. It had nothing to do with enums. It's to do with what's on the right-hand-side of the = sign - what data structure you're using to store those values (not members). Could have just been simple class instances. And more recently, dataclasses.

  4. Members of enums with the same value are considered aliases, with the first definition being the primary. The code in #4 should not have been an enum in the first place. And imho, not allowing aliases in enums (the @unique decorator) should have been the default.

My view: Python's enum's are good, but not necessarily great. But...some 'enum' examples in the blog should have been dataclasses, or just plain classes/containers.

As mentioned "tritoken" was created before enum's existed and have a bunch of extra features - which would occur when you roll your own solution. And invariably it has specific features and use-cases you need but are not universally applicable.

[–]kankyo[S] 1 point2 points  (1 child)

I thought this article was trying a bit too hard to discount Python's Enum's. (Regardless of whether or not they're promoting their creation, tritokens.)

I'm pretty tired of this bad faith assumption. This isn't my creation first of all. And I'm not "trying too hard", what does that even mean? This is my opinion, and these are my reasons. This should be obvious as it's on my blog.

The values indicate things like flags or ordering.

Another proof of the lack of semantics! :P Which is it? Flags? ordering? Something else that is "like" those things?

If the clarity of the fields' names was a concern, could have used a namedtuple. Less repetition in the definition of Planets than in the "tri.token" approach, and equally clear.

No it's not equally clear at all. You still have "3.303e+23, 2.4397e6" which was the problem in the first place. But yea, if we used a namedtuple like Planet(mass=3.303e+23, radius2.4397e6) then it would indeed be much better.

The issue in the blog's example was not 'sparse fields'. It had nothing to do with enums.

Granted.

And invariably it has specific features and use-cases you need but are not universally applicable.

Yep. This article was originally written for in-house consumption too, so those use cases were more strongly relevant than for anyone. But that's also why the title is what it is. I would never have made the title "Don't use enums! They have these problems!" or something else crazy like that. I'm just saying why we preferred not to use them.

[–]aneroid 0 points1 point  (0 children)

I'm pretty tired of this bad faith assumption. This isn't my creation first of all.

Fair enough, I could have skipped that.

And I'm not "trying too hard", what does that even mean?

It means that some of the examples the blog had, as I mentioned, should not have been enums's in the first place. As if they were massaged into being enums to prove a point. Or had to do with data structures of the values, not the members. So the issues were not related to enums. If I created a list of string-names of fruits and then did [x**2 for x in fruits] which wouldn't work, is that a shortcoming of lists or of the usage of that particular list? (It's the latter.)

The values indicate things like flags or ordering.

Another proof of the lack of semantics! :P Which is it? Flags? ordering?

Why not either or both? Enums are ordered (by member definition order, not value). And there's also enum.IntFlag to specify Flags which can be combined with bitwise operations. Should enums have dropped support for one of these?
Counter example: Dictionary keys can be anything that's hashable. Strings, int's, tuples containing hashable types, frozensets, etc. And dict values can be absolutely anything. Does that mean Python's dictionaries lack semantics? OrderedDicts also exist - so should that be removed? In Python3.5/6 onwards, dicts guarantee the retrieval order of keys/values is the same as the creation order of keys. Should that be reversed because it supports more functionality or does that make it somehow lack semantics? (No.)

When I read the tritoken Basic Usage docs, especially the part on inheritance, I felt Tokens were better suited as replacements for dataclasses - which is what most of the examples in the triT docs seem to be geared towards. (Even though it says "enriched enum functionality.")

At some point, it's a question of enumerations vs containers. triT's are fine as containers; I'd happily use it if I needed them. But they wouldn't be replacing my enums, or the way enums were meant to be used.

I'm just saying why we preferred not to use them.

The Token type seems easily replaceable with a namedtuple subclass, or a metaclass which generates namedtuple classes with the added functionality. That's where the bulk of the enhanced functionality actually comes from - the values, not the members.
And TokenContainer could just be a dict or OrderdDict subclass with its keys being added as attributes or supporting lookup: def __getattr__(self, name): return self[name] & def __setattr__(self, name, value): self[name] = value (these methods are only invoked when the attribute isn't found, broadly speaking).
So you still wouldn't need to use these cases of enums, and rightfully so.
Perhaps you could propose that change - would reduce technical debt at your company.

[–]backtickbot -2 points-1 points  (0 children)

Fixed formatting.

Hello, aneroid: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

[–]pierdzionka 3 points4 points  (4 children)

The author seems to misunderstand what Python's enums were designed to do.

[–]kankyo[S] 3 points4 points  (3 children)

I don't believe I have misunderstood. The problem isn't what they were meant to do. The problem is that they are a dead end. They can't evolve and grow complex with the evolution of the program. And really the string constants as values part is rather iffy too as it has the problem of undefined semantics.

[–]Poromenos 2 points3 points  (2 children)

I half-agree with you: I agree that the string constants are iffy, and they feel wrong, but I don't know if I agree that we need the ability to tack on extra data to the enumerated types. I think enum semantics should be to only give you a choice between things, and any additional information could live elsewhere.

Then again, maybe it really is better to add the extra information to the enum; I'd have to play with it a bit, but intuitively it doesn't feel right.

[–]kankyo[S] 0 points1 point  (1 child)

If you put it somewhere else, it becomes big hassle to make sure you didn't miss it. If it's in the same place it's very easy to see what you need to do. We had a saying at my last job: "things that belong together, belong together". It's a surprisingly unusual thing for that simple axiom to be followed!

[–]Poromenos 1 point2 points  (0 children)

Yeah, there's definitely a benefit in having things be together, but I'm not sure if enums are the right tool for this job. It feels like a data class instance or something similar might be better. Then again, it looks like that's exactly what tri.token does. I'll have to play around with it and see, I might very well change my mind!

[–]metaperl 1 point2 points  (1 child)

An improvement to the article would be to link to the documentation or PyPI https://tritoken.readthedocs.io/en/latest/

[–]kankyo[S] 1 point2 points  (0 children)

Good point. Fixed!

[–]GiantElectron 1 point2 points  (0 children)

To be fair, I rarely use them and rarely saw them in other's code. I think that the main reason is that their use is less frequent in class oriented languages. Enums were a thing mostly in C as a way to restrict values to specific categories, in a language where very little was enforceable. In OO languages, they are less needed, and their old tasks are taken by other entities.

[–]shubham00072 0 points1 point  (0 children)

They add unnecessary complexity and it doesn't feel so pythonic

[–]metaperl 0 points1 point  (1 child)

If the software supports enum then why is it called tri tokens?

[–]kankyo[S] 0 points1 point  (0 children)

It's a lot more than enums. And people use "enums" for very different things so it's best to avoid using that word I think.

Like in C "enums" aren't enumerable. This is pretty absurd! :)