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

Hi David, well it seems possible, but I don't see it, so maybe you can give
an example.

So here's the first line from the list:

        if file is not None:

I think we should allow the programmer to use != for that line. Can you
describe a scenario where that line written with != gets the wrong answer?
I can sort of think of how to get it to work wrong, but only with the most
unbelievable contortions.


I'm not saying that uses for "is" don't exist - if someone is coding up
something where detecting if something is a copy is needed, then "is" is
the way. However, requiring "is" for None and False and all these other
super-common singletons .. I don't see why we can't just let them use ==.

Best,

Nick


On Mon, Aug 30, 2021 at 2:04 PM David Mertz, Ph.D. <david.me...@gmail.com>
wrote:

> 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/55RTLVOB23DTFTYGTADQXVPWORKIMAOO/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to