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/