I claimed that uses of "is" where it is needed for correctness
are quite rare. Let me back that up with a little data here.

Just as a random supply of Python code, let's look at
the first four Python modules where the name starts
with a letter from the Python standard modules list
https://docs.python.org/3/py-modindex.html :
abc.py aifc.py argparse.py ast.py (The array module appears to be in C)

Strip out PyDoc and string literals and just grep
for " is " (code included below if you'd like to try
it yourself). Look at those lines - how many of those
uses of "is" are needed for correctness, where the "is"
is really doing something, and how many would work
fine with ==? The resulting lines of code are included below.

There's about 90 uses of is/is-not in this sample.
100% of these uses would work correctly using ==.
Not a single one of these uses actually relies on the "is"
computation for correctness.

PEP8 has forced code to use "is" so much, I think people have
gotten the impression that "is" was really doing something when
that's just not true. These uses of "is" are sort of decorative.

There are cases where the "is" computation is needed, but
they are quite rare.

I am sceptical of the benefit of requiring "is" in all these places where it is not needed.

The other thing I notice looking at this sample, is that
really the use of "is" is dominated by comparisons to None.
I would be satisfied to relax PEP8 just to allow == with None.
As a practical matter, that's the case that dominates.

For the curious, here are the three categories of "is" use
you'll see in the sample.

1. By the far the most common use of is in here is comparing to None,
like we see in the very first line. PEP8 refers to the values False/True
also, but as a practical matter, None is by far the common singleton
for this pattern. There is not a single True/False comparison in this
sample.
    if file is not None:

2. A secondary use of "is" is checking for a particular type or class,
like on these two lines:
    if type(items) is list:
    if type_func is FileType:

3. Lastly we see "is" checking to see if a value matches
a constant, like this
    if self.heading is not SUPPRESS and self.heading is not None:
    if operator_precedence is not _Precedence.FACTOR:

I assume that if we have some module.CONST = 2000 constant,
the module can accept client uses of either module.CONST or 2000,
though clearly using module.CONST is better stylistically.


---

' is ' lines from: abc.py aifc.py argparse.py ast.py


        if file is not None:
        self._aifc = 1      # AIFF-C is default
        if self._file is None:
        if self._form_length_pos is not None:
        if self._form_length_pos is not None:
    if mode is None:
    if items is None:
    if type(items) is list:
        if width is None:
            if self.parent is not None:
            if self.parent is not None:
            if self.heading is not SUPPRESS and self.heading is not None:
        if text is not SUPPRESS and text is not None:
        if usage is not SUPPRESS:
        if action.help is not SUPPRESS:
                        if part and part is not SUPPRESS])
        if prefix is None:
        if usage is not None:
        elif usage is None and not actions:
        elif usage is None:
                    if prefix is not None:
                    if prefix is not None:
            if action.help is SUPPRESS:
        text = ''.join([item for item in parts if item is not None])
        if action.metavar is not None:
        elif action.choices is not None:
        if action.nargs is None:
            if params[name] is SUPPRESS:
        if params.get('') is not None:
            if action.default is not SUPPRESS:
    if argument is None:
        if self.argument_name is None:
        if help is not None and default is not None:
        if const is not None and nargs != OPTIONAL:
        if const is not None and nargs != OPTIONAL:
        if count is None:
        if version is None:
        if kwargs.get('') is None:
        if self.dest is not SUPPRESS:
                              if arg is not None])
            if action.dest == dest and action.default is not None:
            elif self.argument_default is not None:
        if type_func is FileType:
        if dest is None:
        if prog is None:
        if self._subparsers is not None:
        if kwargs.get('') is None:
        if args is None:
        if namespace is None:
            if action.dest is not SUPPRESS:
                    if action.default is not SUPPRESS:
        if self.fromfile_prefix_chars is not None:
                if option_tuple is None:
            if argument_values is not action.default:
            if argument_values is not SUPPRESS:
                if action is None:
                if explicit_arg is not None:
                    if (action.default is not None and
                        action.default is getattr(namespace, action.dest)):
                             if action.help is not SUPPRESS]
        if match is None:
            if msg is None:
            if match is not None:
        if nargs is None:
                if self.usage is None:
            if action.default is not None:
        if action.choices is not None and value not in action.choices:
        if file is None:
        if file is None:
            if file is None:
    elif feature_version is None:
        if indent is not None:
                if value is None and getattr(cls, name, ...) is None:
                    if value is None and getattr(cls, name, ...) is None:
    if indent is not None and not isinstance(indent, str):
            if value is not None or (
            if getattr(node, '', None) is None:
            if getattr(node, '', None) is None:
            and (end_lineno := getattr(child, "", 0)) is not None
        if node.end_lineno is None or node.end_col_offset is None:
        if type_name is None:
        if type_name is not None:
                        if value is None:
                if new_node is None:
        if cls is Ellipsis:
        elif value is ...:
            if k is None:
            if operator_precedence is not _Precedence.FACTOR:
        if node.arg is None:


----
stripcode.py

#!/usr/bin/env python3

"""
Echo python files input with most of the strings
and comments removed, so you can grep for code patterns.
Nick Parlante
This code is placed in the public domain
"""

import sys
import re


def strip_code(code):
    """Return code text with most string literals and comments removed"""
    code = re.sub(r"'''.*?'''", "''", code, flags=re.DOTALL)
    code = re.sub(r'""".*?"""', "''", code, flags=re.DOTALL)
    code = re.sub(r"'.*?'", "''", code)  # won't work right with \'
    code = re.sub(r'".*?"', '""', code)
    code = re.sub(r'^\s*#.*$', '', code, flags=re.MULTILINE)  # only comments on line by self
    return code
   

def print_strip(filename):
    """Print stripped version of given file"""
    with open(filename) as f:
        print(strip_code(f.read()), end='')

       
def main():
    args = sys.argv[1:]
   
    for fname in args:
        print_strip(fname)

if __name__ == '__main__':
    main()




On Mon, Aug 30, 2021 at 11:32 AM Nick Parlante <nick@cs.stanford.edu> wrote:
Hi there python-ideas - I've been teaching Python as a first
programming language for a few years, and from that experience I want
to propose a change to PEP8. I'm sure the default position for PEP8 is
to avoid changing it. However, for this one rule I think a good case
can be made to make it optional, so let me know what you think.

Let me start with what I've learned from teaching students in Java and
now in Python. In Java, you use == for ints, but you need to use
equals() for strings. Of course students screw this up constantly,
using == in a context that calls for equals() and their code does not
work right. Then for Java arrays a different comparison function is
required, and so it goes. To teach comparisons in Python, I simply say
"just use ==" - it works for ints, for strings, even for lists.
Students are blown away by how nice and simple this is. This is how
things should work. Python really gets this right.

So what is the problem?

The problem for Python is what I will call the "mandatory-is" rule in
PEP8, which reads:

Comparisons to singletons like None should always be done with is or
is not, never the equality operators.

For the students, this comes up in the first week of the course with
lines like "if x == None:" which work perfectly with == but should use
is/is-not for PEP8 conformance.

My guess is that this rule is in PEP8 because, within a Python
implementation, it is within the programmer's mental model that, say,
False is a singleton. The mandatory-is rule is in PEP8 to reinforce
that mental model by requiring the is operator. Plus it probably runs
a tiny bit faster.

However, for "regular" Python code, not implementing Python, forcing
the use of is instead of the simpler == is unneeded and unhelpful (and
analogously forcing "is not" when != works correctly). What is the
benefit of forcing the is operator there? I would say it spreads an
awareness of the details of how certain values are allocated within
Python. That's not much of a benefit, and it's kind of circular. Like
if programmers were permitted to use ==, they wouldn't need to know
the details of how Python allocates those values. Being shielded from
implementation details is a Python strength - think of the Java vs.
Python story above. Is Java better because it builds an awareness in
the programmer of the different comparison functions for different
types? Of course not! Python is better in that case because it lets
the programmer simply use == and not think about those details.
Understanding the singleton strategy is important in some corners of
coding, but forcing the is operator on all Python code is way out of
proportion to the benefit.

As a practical matter, the way this comes up for my students is that
IDEs by default will put warning marks around PEP8 violations in their
code. Mostly this IDE-coaching is very helpful for students learning
Python. For example, It's great that beginning Python programmers
learn to put one space around operators right from the first day.
Having taught thousands of introductory Python students, the one PEP8
rule that causes problems is this mandatory-is rule.

As a teacher, this is especially jarring since the "just use ==" rule
is so effortless to use correctly. In contrast, the mandatory-is rule
adds a little pause where the programmer should think about which
comparison operator is the correct one to use. It's not hard, but it
feels unnecessary.

As a contrasting example, in the language C, programmers need to
understand == vs. is right from the first day. You can't get anything
done in C without understanding that distinction. However that is just
not true for regular (not-Python-implementation) Python code, where ==
works correctly for the great majority of cases.

Here is my proposal:

Add the following parenthetical to the mandatory-is rule: (this rule
is optional for code that is not part of an implementation of Python).

So in effect, programmers outside of a Python implementation can
choose to use == or is for the "if x == None:" case. In this way, PEP8
conforming code before the change is still conforming. Moving forward,
I would expect that regular code will trend towards using == in such a
case, reserving is for the rare cases where it is needed for
correctness.

PEP8 was originally just for Python implementations, so why is this
change needed? Because as a practical matter, the vast majority of
code that is using PEP8 is not part of a Python implementation. This
may not have been the original mission of PEP8, but it is how things
have worked out.

Now we are in a situation where the rules in PEP8 are sent out to this
ocean of Python programmers of many different ability levels writing
regular code that is not a Python implementation. One could imagine a
separate PEP800 style guide for regular code, but we don't need to do
that, because in almost all cases PEP8 works great for regular code. I
have taught thousands of new Python programmers, and the only place
where PEP8 serves them poorly is this mandatory-is rule. Therefore
instead of a separate style guide for regular code, I propose an
exception for this one problem rule.

Ultimately this comes down to the question - should PEP8 push regular,
not-Python-implementation code to use is for singletons in cases where
== works perfectly? Seeing how effortless it is for programmers to use
== as their first choice, I think PEP8 should allow that practice.

Best,

Nick