Should enum instances be compared by identity or equality?

Question:

PEP 8 Programming Recommendations says:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

According to the docs, enum members are singletons. Does that mean they should also be compared by identity?

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# like this?
if color is Color.RED:
    ...

# or like this
if color == Color.RED:
    ...

When using equality operators, I haven’t noticed any issues with this to warrant such strong wording as PEP 8. What’s the drawback of using equality, if any? Doesn’t it just fall back to an identity-based comparison anyway? Is this just a micro-optimisation?

Asked By: wim

||

Answers:

First, we can definitely rule out x.value is y.value, because those aren’t singletons, those are perfectly ordinary values that you’ve stored in attributes.

But what about x is y?

First, I believe that, by “singletons like None”, PEP 8 is specifically referring to the small, fixed set of built-in singletons that are like None in some important way. What important way? Why do you want to compare None with is?

Readability: if foo is None: reads like what it means. On the rare occasions when you want to distinguish True from other truthy values, if spam is True: reads better than if spam == True:, as well as making it more obvious that this isn’t just a frivolous == True used by someone improperly following a C++ coding standard in Python. That might apply in foo is Potato.spud, but not so much in x is y.

Use as a sentinel: None is used to mean “value missing” or “search failed” or similar cases. It shouldn’t be used in cases where None itself can be a value, of course. And if someone creates a class whose instances compare equal to None, it’s possible to run into that problem without realizing it. is None protects against that. This is even more of a problem with True and False (again, on those rare occasions when you want to distinguish them), since 1 == True and 0 == False. This reason doesn’t seem to apply here—if 1 == Potato.spud, that’s only because you intentionally chose to use an IntEnum instead of an Enum, in which case that’s exactly what you want…

(Quasi-)keyword status: None and friends have gradually migrated from perfectly normal builtin to keyword over the years. Not only is the default value of the symbol None always going to be the singleton, the only possible value is that singleton. This means that an optimizer, static linter, etc. can make an assumption about what None means in your code, in a way that it can’t for anything defined at runtime. Again, this reason doesn’t seem to apply.

Performance: This really is not a consideration at all. It might be faster, on some implementations, to compare with is than with ==, but this is incredibly unlikely to ever make a difference in real code (or, if it does, that real code probably needs a higher-level optimization, like turning a list into a set…).

So, what’s the conclusion?

Well, it’s hard to get away from an opinion here, but I think it’s reasonable to say that:

  • if devo is Potato.spud: is reasonable if it makes things more readable, but as long as you’re consistent within a code base, I don’t think anyone will complain either way.
  • if x is y:, even when they’re both known to be Potato objects, is not reasonable.
  • if x.value is Potato.spud.value is not reasonable.
Answered By: abarnert

PEP 8 says:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

I disagree with abarnert: the reason for this isn’t because these are built-in or special in any way. It’s because in these cases you care about having the object, not something that looks like it.

When using is None, for example, you care about whether it’s the None that you put there, not a different None that had been passed in. This might be difficult in practice (there is only one None, after all) but it does sometimes matter.

Take, for example:

no_argument = object()
def foo(x=no_argument):
    if x OP no_argument:
        ...
    ...

If OP is is, this is perfectly idiomatic code. If it’s ==, it’s not.

For the same reason, you should make the decision as so:

  • If you want equality to duck type, such as with IntEnum or an Enum that you might want to subclass and overwrite (such as when you have complex enum types with methods and other extras) it makes sense to use ==.

  • When you are using enums as dumb sentinels, use is.

Answered By: Veedrac

From https://docs.python.org/3/library/enum.html#module-enum:

Within an enumeration, the members can be compared by identity

In particular, from https://docs.python.org/3/howto/enum.html#comparisons :

Enumeration members are compared by identity

and

Equality comparisons are defined though

Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.