Re: [Python-Dev] PEP 572: intended scope of assignment expression

2018年7月05日 08:45:06 -0700

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 <[email protected]>:
> *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
[email protected]
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to