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?
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.
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
.
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
PEP 8 Programming Recommendations says:
Comparisons to singletons like None should always be done with
is
oris 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?
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 bePotato
objects, is not reasonable.if x.value is Potato.spud.value
is not reasonable.
PEP 8 says:
Comparisons to singletons like None should always be done with
is
oris 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
.
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