I agree that, of course, it's possible to construct a class where == has
this kind of weird behavior, like claiming that == to None is True.

So are you saying that the reason PEP8 should forbid writing the line like
this

    if x == None:

is because x might be such a class?

Best,

Nick




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

> Chris Angelica and Matthias Bussonnier have provided numerous examples of
> both "custom" objects and widely used libraries where the distinction
> between equality and Identity is important.
>
> Nearly every example you found is such a case. There is an attribute or a
> variable (often a formal parameter) that uses None as a sentinel for "not
> set" or "missing". They do that because I'm other circumstances they WILL
> be set.
>
> It is true that if 'myvar' happens to be set 37 or "hello", then 'myvar ==
> None' or 'myvar is None' are equivalent. But that only works for a limited
> range of possible values.
>
> As I noted, the same day you teach students about mutable collections, you
> need to teach them the difference between Identity and equality. If you
> fall to do so, you do them a disservice.
>
> On Mon, Aug 30, 2021, 5:31 PM Nick Parlante <n...@cs.stanford.edu> wrote:
>
>> 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/OWZHEHPXFOOEDUWGEAJDEX2BLFLBQBBN/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to