On Wednesday, April 25, 2018, Łukasz Langa <lukasz@langa.pl> wrote:
On 25 Apr, 2018, at 5:20 PM, Chris Angelico <rosuav@gmail.com> wrote:
On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov <yselivanov.ml@gmail.com> wrote:
Just yesterday this snippet was used on python-dev to show how great the new syntax is:
my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
To my eye this is an anti-pattern. One line of code was saved, but the other line becomes less readable. The fact that 'buf' can be used after that line means that it will be harder for a reader to trace the origin of the variable, as a top-level "buf = " statement would be more visible.
Making 'buf' more visible is ONLY a virtue if it's going to be used elsewhere. Otherwise, the name 'buf' is an implementation detail of the fact that this function wants both a buffer and a size.
You're claiming that `:=` is nicer in this situation because it's less prominent than regular assignment and thus doesn't suggest that the name stays visible later.
But as others said, `:=` *does* make the name visible later until the enclosing scope ends. In fact, a large part of its appeal is that you can use the result later (as in the `re.search()` example). Will it be visible enough to the reaser in those cases then?
There seems to be a conflict there.
The question of assignment visibility also makes me think about unintentional name shadowing::
buf = some_value
... # 20 lines
my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
... # 20 lines
buf # <-- What value does this have?
Even if we're not using the call pattern, there can be plenty of logic tests which aren't very obvious::
buf = some_value
... # 20 lines
if node.parent is not None and (buf := node.parent.buffer): ... # 10 lines
... # 20 lines
buf # <-- What value does this have?
This is even more interesting because now `buf` isn't rebound *always*.
So if I'm confused about an unexpected change in value of `buf`, I'll skim the code, fail to find the assignment, and then grep for `buf =` and also fail to find the assignment. Yes, I could have searched for just `buf` instead but that will give me too many false positives, especially if I'm using a primitive text editor search or don't know about \b in regular expressions.
Debugging this can be confusing. I know it can since a similar annoyance can be observed with the magic pseudo-scope of `except`::
err = some_value try: ... except Error as err: ...
err # <-- now sometimes it's not defined
Just like Barry, I debugged a few cases of this in the past and within larger functions this can be hard to find.
Would this make it easier to put too much code on one line? Is there a good way to get *branch coverage* stats instead of just *line coverage*? Someone can probably explain with some tested pretty code for me why this would be necessary or helpful; why it wouldn't make line coverage stats more misleading for the sake of lazy?
-- Ł