First, we can definitely exclude x.value is y.value , because these are not singletones, these are completely ordinary values that you saved in the attributes.
But what about x is y ?
First of all, I think that “singleton like None” PEP 8 refers specifically to a small fixed set of built-in singletones that are somehow similar to None . What is the important way? Why do you want to compare None with is ?
Readability: if foo is None: reads like what it means. In rare cases when you want to distinguish True from other right values, if spam is True: reads better than if spam == True: and also makes it more obvious that it is not just frivolous == True used by someone inappropriate way C ++ standard in Python. This can be used in foo is Potato.spud , but not so much in x is y .
Use as a sentinel: None used to indicate "value missing" or "search not completed" or similar cases. It should not be used in cases where None itself can be a value, of course. And if someone creates a class whose instances are compared to None , he can deal with this problem without realizing it. is None protects against this. This is an even bigger problem with True and False (again, in those rare cases when you want to distinguish them), since 1 == True and 0 == False . This reason does not seem to apply here if 1 == Potato.spud , which is only because you intentionally decided to use IntEnum instead of Enum , in which case exactly what you want ...
Keyword Status
(Quasi-): None , and friends gradually switched from a perfectly normal inline to a keyword over the years. Not only will the default value of the None symbol be always single, the only possible value is singleton. This means optimizer, static linter, etc. They may make an assumption about what None means in your code, so that it cannot for anything specific at runtime. Again, this reason does not seem to apply.
Performance: This is really not a consideration at all. In some implementations, it may be faster to compare with is than with == , but it is incredibly unlikely to ever change the real code (or, if that happens, this real code probably needs a higher level of optimization, such as list conversion into the set ...).
So what is the conclusion?
Well, it's hard for me to get away from the 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 agree on the code base, I don't think anyone will complain anyway.if x is y: even if they are both known as Potato objects, are not reasonable.if x.value is Potato.spud.value not reasonable.