> > 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/