[Python-Dev] (name := expression) doesn't fit the narrative of PEP 20

Wes Turner wes.turner at gmail.com
Wed Apr 25 23:07:01 EDT 2018


On Wednesday, April 25, 2018, Łukasz Langa <lukasz at langa.pl> wrote:

>
> On 25 Apr, 2018, at 5:20 PM, Chris Angelico <rosuav at gmail.com> wrote:
>
> On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> <yselivanov.ml at 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?


>
> -- Ł
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20180425/0a0dffb6/attachment.html>


More information about the Python-Dev mailing list