`is` and special literals

Just noticed that

    s = d.get(key, None)
    if s is "":

now produces a SyntaxWarning "is" with a literal. Did you mean "==", and tools like pylint encourage users to replace this with Truth testing.

For years, I’ve ended up fixing bugs caused by people overlooking the frequent ternary nature of Python objects.

rc = popen_process.poll()
if not rc:
    return "It worked!"
... error handling ...

and in production, this fails when the child process takes a little longer to give up its exit code, or hangs, and poll() returns None.

Similar bugs in parsing configurations, where “None” and “empty string” mean different things.

While this guidance is certainly valid for testing arbitrary literals

    if s is "hello":  # can it ever be?

is it worth remembering that empty string is a special case of its own?

    import sys
    a = ["abc"[:0], "cde"[3:], "hello".replace("hel", '').replace('lo', ''), u''.encode('utf-8'), ' '.strip(), '/'.split('/')[0], ''.upper(), ''+'', f'{sys.platform[0]}']
    b = [s for s in a if s is '' and id(s) == id('')]
    assert len(a) == len(b)

but no such SyntaxWarning for True, False, or None.

'' and 0 are equal edge cases where engineers are likely to need to distinguish between “absent value” and “value absent”. I see this change producing a lot of sneaky and “CNR” bugs over the next few years.

<stdint>:4: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if s is '':
<stdin>:6: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if s is 0:

Is the intent here that engineers must write:

if s is not None and not s:

because that is not equivalent to

if s is 0:

or

if s is '':

The language simply does not guarantee that 0 and '' are singletons.

3 Likes

It is wrong for linters to suggest replacing if s == 0 with if not s, so we need to fix those linters. Changing to interpreter to accommodate a linter bug feels totally backwards to me.

3 Likes

What would be required for it to do so? Is there an implementation that doesn’t? Is there a way in CPython to produce a str that not is ''?

I think it’s very easy to teach/learn/read:

if english_kniggit is not "":
and
if your_mother is not 0:

as indicative that the author was anticipating the possibility that these values could be None, or other types entirely. It’s one of those programming unicorns, the opportunity to audit intentions in the code itself :slight_smile:

Why not write != instead of ‘is not’? because then pylint complains? Shame on it.

1 Like

The problem with making the empty string and 0 special cases, and
having the language guarantee that they are singletons so that

s is ""
n is 0

works in every Python implementation, is that it trains beginners to
expect to use is for strings and numbers:

if s is prefix

if n is value

are now landmines, ready to explode depending on the values of prefix
and value, and the version and implementation of the interpreter.

Right now, we already get lots of beginners confused why this appears to
work:

if s is "alphabet"

(or at least, it sometimes works, enough that they think it should
work all the time) but this doesn’t:

if s is "black and white"

and we have to explain about the implementation details of the
interpreter, that it caches some strings (but not all) and some small
ints (but the definition of small may vary from version to version), and
never caches floats (at least, not yet), and so on.

Much better to just teach beginners the simple rule:

  • Never use “is” to test if two things are equal;

  • Only use “is” to compare with None.

(Any other uses of None are for advanced usages.)

We could make it a language guarantee that “” and 0 are always
singletons, but that would just make it harder to teach people to stop
using is when they should be using ==.

!= will work fine with pylint. It will even emit a warning to suggest the use of = / != instead of is / is not with literals.

var = 0
if var is 0:  # 'literal-comparison' warning
    pass
if var == 0 or var != 0:
    pass

empty_string = ""
if empty_string is "":  # 'literal-comparison' warning
    pass
if empty_string == "":
    pass

I kinda wish we’d made == None special, so then the simple rule for beginners is even simpler, and instead the rule for people implementing __eq__ (who are presumably more advanced) is a little more complex. (But I don’t own the time machine…)

1 Like

is that it trains beginners to expect to use is for strings and numbers:

I realize that, but those would be perfectly valid cases for the SyntaxWarning that’s been introduced. So that “is” is only ever for determining two things are the same, with the knowledge that Empty String is a Thing and 0 is a Thing.

“Is this, and is this only, the empty string, and not something comparable to the empty string.”

People also routinely use ‘is’ when dealing with boolean ternaries:

    if s is False:  # not the same as `not s`, because None, 0 and "" mean other things

Also, did you mean and other uses of "is" are for advanced usages? Obviously, a lot of people use it for constant matches

   if s is SpecialValue:

if that was easy to teach, maybe ruby would only be really awful and not totally awful.

For beginners, using if s is SpecialValue counts as an advanced usage
:slight_smile: (except in the case of testing for None, which is so common that
they need to learn it early on).

The tricky part of the bool idioms if flag is True (or False) is that
it is sometimes hard to tell whether it is exactly what the user wants,
because they literally only want the True singleton and not any other
truthy value (and so is good, correct code), or the user doesn’t
understand boolean logic and is being needlessly inefficient:

if flag
if flag is True
if (flag is True) is True
if ((flag is True) is True) is True

If you think that boolean values always need to be tested with “is
True”, then where do you stop?

In languages like Pascal that only have actual real bools, then it is
always pointless to say “if flag == true” since “if flag” does the same.
But the situation is a little less clear in Python. But one good hint is
whether the caller actually realises that they have three conditions to
handle, not two:

if flag is True:
    ...
elif flag is False:
    ...
else:
    # All other cases.

If they don’t have that three-way switch, then they are probably doing
it wrong:

if flag is False:
    # only the literal False singleton
else:
    # any truthy or falsey value except False
1 Like

It would break mock.Any and some advanced tricks.

I do not think there is a real problem with is None for beginners.

1 Like