After "Assignment expression and coding style: the while True case", here is the part 2: analysis of the "if (var := expr): ..." case.
2018-07-05 14:20 GMT+02:00 Victor Stinner <vstin...@redhat.com>: > *intended* scope. I generated the giant pull request #8116 to show where I consider that "if (var := expr): ..." would be appropriate in the stdlib: https://github.com/python/cpython/pull/8116/files In short, replace: var = expr if var: ... with: if (var := expr): ... I used a script to replace "var = expr; if var: ..." with "if (var := expr): ...". I restricted my change to the simplest test "if var:", other conditions like "if var > 0:" are left unchaned to keep this change reviewable (short enough). The change is already big enough (62 files modified) to have enough examples! Then I validated each change manually: (*) I reverted all changes when 'var' is still used after the if. (*) I also reverted some changes like "var = regex.match(); if var: return var.group(1)", since it's already handled by my PR 8097: https://github.com/python/cpython/pull/8097/files (*) Sometimes, 'var' is only used in the condition and so has been removed in this change. Example: ans = self._compare_check_nans(other, context) if ans: return False return self._cmp(other) < 0 replaced with: if self._compare_check_nans(other, context): return False return self._cmp(other) < 0 (Maybe such changes should be addressed in a different pull request.) Below, some examples where I consider that assignment expressions give a value to the reader. == Good: site (straighforward) == env_base = os.environ.get("PYTHONUSERBASE", None) if env_base: return env_base replaced with: if (env_base := os.environ.get("PYTHONUSERBASE", None)): return env_base Note: env_base is only used inside the if block. == Good: datetime (more concise code) == New code: def isoformat(self, timespec='auto'): s = _format_time(self._hour, self._minute, self._second, self._microsecond, timespec) if (tz := self._tzstr()): s += tz return s This example shows the benefit of the PEP 572: remove one line without making the code worse to read. == Good: logging.handlers == def close(self): self.acquire() try: sock = self.sock if sock: self.sock = None sock.close() logging.Handler.close(self) finally: self.release() replaced with: def close(self): self.acquire() try: if (sock := self.sock): self.sock = None sock.close() logging.Handler.close(self) finally: self.release() == Good: doctest == New code: # Deal with exact matches possibly needed at one or both ends. startpos, endpos = 0, len(got) if (w := ws[0]): # starts with exact match if got.startswith(w): startpos = len(w) del ws[0] else: return False if (w := ws[-1]): # ends with exact match if got.endswith(w): endpos -= len(w) del ws[-1] else: return False ... This example is interesting: the 'w' variable is reused, but ":=" announces to the reader that the w is only intended to be used in one if block. == Good: csv (reuse var) == New code: n = groupindex['quote'] - 1 if (key := m[n]): quotes[key] = quotes.get(key, 0) + 1 try: n = groupindex['delim'] - 1 key = m[n] except KeyError: continue if key and (delimiters is None or key in delimiters): delims[key] = delims.get(key, 0) + 1 As for doctest: "key := ..." shows that this value is only used in one if block, but later key is reassigned to a new value. == Good: difflib == New code using (isjunk := self.isjunk): # Purge junk elements self.bjunk = junk = set() if (isjunk := self.isjunk): for elt in b2j.keys(): if isjunk(elt): junk.add(elt) for elt in junk: # separate loop avoids separate list of keys del b2j[elt] -*-*-*- == Borderline? sre_parse (two conditions) == code = CATEGORIES.get(escape) if code and code[0] is IN: return code replaced with: if (code := CATEGORIES.get(escape)) and code[0] is IN: return code The test "code[0] is IN" uses 'code' just after it's defined on the same line. Maybe it is surprising me, since I'm not sure to assignment expressions yet. -*-*-*- == BAD! argparse (use after if) == Ok, now let's see cases where I consider that assignment expressions are inappropriate! Here is a first example. help = self._root_section.format_help() if help: help = self._long_break_matcher.sub('\n\n', help) help = help.strip('\n') + '\n' return help 'help' is used after the if block. == BAD! fileinput (use after if) == line = self._readline() if line: self._filelineno += 1 return line if not self._file: return line 'line' is used after the first if block. == BAD! _osx_support (reuse var, use after if) == def _supports_universal_builds(): osx_version = _get_system_version() if osx_version: try: osx_version = tuple(int(i) for i in osx_version.split('.')) except ValueError: osx_version = '' return bool(osx_version >= (10, 4)) if osx_version else False Surprising reusage of the 'osx_version' variable, using ":=" would more the code even more confusing (and osx_version is used after the if). Victor _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com