EVERY line you found would behave incorrectly if changed to "x == None".

Admittedly, doing so would require setting the various variables and
attributes to somewhat uncommon objects that act "funny" when comparing to
None.  Or when doing equality comparisons in general.

As others have said, learning the difference between equality and Identity
is relevant pretty early in learning to program. No, not in the first day.
Probably not on the second. But PEP 8 shouldn't be distorted just for
people in their first month of programming... who are unlikely to be
contributors to CPython so soon, as well.

As soon as you teach the difference between:

a, b = [], []

and:

a = b = []

That distinction has become very important. Knowing operators to express
that distinction is thereby important.

On Mon, Aug 30, 2021, 4:51 PM Nick Parlante <n...@cs.stanford.edu> wrote:

> 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 <n...@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
>>
> _______________________________________________
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-ideas@python.org/message/UZOUHF2C3GP6ZTZUN6RWD4I3VDXBWA2O/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/4DNR6V4M4YWLWHIS7P7XXETDCNXHRPQH/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to