`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:


if s is '':

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


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.


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 "":
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.


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

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
if var == 0 or var != 0:

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

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:
    # 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
    # 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

Fully agree with the first bullet, and I agree with the sentiment of the second, but not the specific narrow use of None. There are other singletons that can be compared with is. The rules I use are:

  • == and != compare equality
  • is and is not compare identity

This still leaves me confused about True and False, though.

Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from dis import dis

In [2]: from typing import Any

In [3]: def f(a: Any) -> Any:
   ...:     return a
   ...: def f_is(a: Any) -> None:
   ...:     assert f(a) is True
   ...: def f_eq(a: Any) -> None:
   ...:     assert f(a) == True

In [4]: dis(f_is)
  4           0 LOAD_GLOBAL              0 (f)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 LOAD_CONST               1 (True)
              8 IS_OP                    0
             10 POP_JUMP_IF_TRUE         8 (to 16)
             12 LOAD_ASSERTION_ERROR
             14 RAISE_VARARGS            1
        >>   16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

In [5]: dis(f_eq)
  6           0 LOAD_GLOBAL              0 (f)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 LOAD_CONST               1 (True)
              8 COMPARE_OP               2 (==)
             10 POP_JUMP_IF_TRUE         8 (to 16)
             12 LOAD_ASSERTION_ERROR
             14 RAISE_VARARGS            1
        >>   16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

In [6]: f_is(1)
AssertionError                            Traceback (most recent call last)
<ipython-input-6-c969a2eafce0> in <module>
----> 1 f_is(1)

<ipython-input-3-5b5aed936a43> in f_is(a)
      2     return a
      3 def f_is(a: Any) -> None:
----> 4     assert f(a) is True
      5 def f_eq(a: Any) -> None:
      6     assert f(a) == True


In [7]: f_eq(1)

In [8]:

I’m a little confused about what you’re confused about, sorry :joy: Is there a reason you’d expect the arbitrary integer 1 to be the same object as the singleton True? You’d use if a is True when you want to check if the value is the literal True (as is needed sometimes), as opposed to merely truthy, in which case you’d use simply if a (there’s never any need to use if a == True as a it is more verbose, less efficient and creates ambiguity, as @steven.daprano explains). The same is the case with False.

The 1 here is the index of the const, not the value. The value is shown in parentheses (i.e. True).

See this example:

>>> import dis                                     
>>> def f():                                       
...     x = 1                                           
...     y = True                                        
>>> dis.dis(f)                                     
  1           0 RESUME                   0         
  2           2 LOAD_CONST               1 (1)     
              4 STORE_FAST               0 (x)     
  3           6 LOAD_CONST               2 (True)  
              8 STORE_FAST               1 (y)     
             10 LOAD_CONST               0 (None)  
             12 RETURN_VALUE                       
>>> f.__code__.co_consts
(None, 1, True)

This shouldn’t be confusing because I recommend you almost never compare against True and False explicitly. I always call that out in code reviews.

The one exception is when you might have ternary values, such as True, False, or None but even there I’d generally compare identity against None and let truthy or falsey conditionals take it from there.