It’s got to be a keyword, that’s how it can work with is not, not also being a keyword.
Also there’s a quite ubiquitous use of is that justifies its inclusion; compare a is None to a == None to not a… only by testing the identity can you know for certain that None was passed.
a is None
a == None
You put that very clearly, here and in the other comments; I learned something today.
One other ubiquitous use of is: type inspection, like if type(a) is MyClass or if type(a) is str.
if type(a) is MyClass
if type(a) is str
(Some of the time isinstance(a, MyClass) will also do, but if you want to exclude the possibility of subclass instances, only a type identity check suffices.
Ooonn the other hand, one could also argue that is tempts people into checking for identity when a subclass would also suffice; and that this may needlessly prevent interoperation with otherwise-valid subclasses. Hm. I really like the keyword, though, especially compared to ===)
Note that you don’t normally need is for this sort of type inspection, as type(a) == MyClass works fine pretty much always. I think the only time it wouldn’t work correctly is if one of the classes involved had a metaclass that overrode __eq__ and did something perverse with it. I think that the cases where you really need is are uncommon; you need to care about object identity and be in a situation where something is overriding == in a way that affects the comparison you care about. Python definitely should have something that checks object identity in the way that is does, but I don’t know if it needs to be a tempting keyword instead of a built-in function.
type(a) == MyClass
(I’m the author of the linked to article.)
Every language must have a means of checking both identity and equality … they are fundamentally different things. The choice to make both of them an operator is quite common, the only real distinction Python makes by choosing == and is over, say, == and === is that promotion to also being a keyword.
And just because you find it to be uncommon does not mean that it is, in fact, uncommon. I use is all the time, for instance with enum.Enum members, to properly handle default values that need to be mutable, and to identify sentinel values in iterators (usually None, but not if None is a legitimate value of the iterator) … it’s also extremely necessary to things like the Singleton pattern in Python.
Moreover you’re throwing out the baby with the bathwater … sure, exactly as you said you can use type(a) == MyClass pretty much anywhere you might use type(a) is MyClass, but why would you? Why necessarily invoke the performance cost of equality comparison? The underlying cost of is is near zero, but by using == you force lookup and execution of MyClass.__eq__ and, if that returns False or NotImplemented you further force lookup and execution of type(a).__eq__ … both of which could be implemented in such a way as to be unbearably time consuming.
type(a) is MyClass
See the question of identity is, precisely, “are these the same thing” rather than “do these share the same attributes” … they’re fundamentally different questions, and when all you care about is identity, why bother asking for equality?
Arguing that it has to be a keyword because otherwise you would have to write it differently is a weird argument to me. It just means that it would be written not is(a, b) instead, which might not read as nicely, but that’s a different argument.
not is(a, b)
Perhaps I should have been more specific: is needs to be either a reserved keyword or an operator (in effect it’s both, though it’s not truly an operator because, like and, or, and not it cannot be overloaded) precisely because Guido et all intended it to be used as a syntactic construct… it cannot be a function because it would then have to be used with the call syntax, be able to be assigned to a name, and overloadable in user code. There is no desire for an overloadable identity operation, indeed allowing it to be overloaded would break fundamental language semantics. The same is true of None, which is why it’s also a keyword, though it could just be a globally available variable… in Python the argument for promoting to an operator is “should this bind values around it into an operation” and for promotion to a keyword is “should the meaning of this never even potentially change during runtime” and (optionally) “should this modify other keywords” … in the case of is the answer to all of those is “yes”, so the only option was to make it a keyword.
It seems to me like the same arguments would mean that isinstance should also be a keyword though?
Sure, it could be extended to any number of things — Python 2 didn’t see True or False as keywords, but that protection was extended to them in Python 3 — but would it add any semantic value to do so, by stripping call semantics from it and allowing it to be used as an operator or to directly interact with other keywords?
Some keywords have been downgraded (most notably print), but that was because there was added value in doing so, and also a colorable argument for overloading existed. The simple fact is that identity testing is a fundamental operation — much more so than introspecting class phylogeny — and the benefits of being a keyword far outweigh the minor confusion it can cause in relative newbies, while there’d be no advantage to removing it except for people who don’t know why it’s fundamental.
In other languages you’ve got equality as == and identity as ===… frankly we’ve got it easy with is.
is being built in means you know it can’t be overridden.
It being a function would be ugly