[Python-ideas] A real life example of "given"

Steven D'Aprano steve at pearwood.info
Wed May 30 21:01:05 EDT 2018

On Thu, May 31, 2018 at 10:05:33AM +1000, Chris Angelico wrote:
> On Thu, May 31, 2018 at 9:53 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> >> There is no nice, equivalent := version as far as I can tell.
> >
> > Given (pun intended) the fact that you only use transformed_b in a
> > single place, I don't think it is necessary to use := at all.
> >
> > z = {a: transform(b) for b in bs for a in as_}
> >
> > But if you really insist:
> >
> > # Pointless use of :=
> > z = {a: (transformed_b := transform(b)) for b in bs for a in as_}
> >
> That's the subtlety of the 'given' usage here. You fell for the same
> trap I did: thinking "it's only used once".

But it is only used once. I meant once per loop.

It isn't used in the "for a in as_" inner loop, there's no "if 
transformed_b" condition, and it only is used once in the key:value part 
of the comprehension.

> Actually, what he has is equivalent to:
> z = {a: tb for b in bs for tb in [transform(b)] for a in as_}

Which also uses tb only once, making it a Useless Use Of Assignment.

(I assume we're not calling transform() for some side-effect, like 
logging a message, or erasing your hard drive.)

> which means it evaluates transform(b) once regardless of the length of
> as_. 

Ah yes, I see what you mean. Expanded to a loop:

    for b in bs:
        tb = transform(b)
        for a in as_:
            z[a] = tb

It's a little ugly, but there's a trick I already use today:

py> [x+y for x in "abc" if print(x) or True for y in "de"]
['ad', 'ae', 'bd', 'be', 'cd', 'ce']

So we can adapt that to assignment instead of output:

# Don't do this!
z = {a: tb for b in bs if (tb := transform(b)) or True for a in as_}

But I wouldn't do that. If I'm concerned about the call to transform 
(because it is super expensive, say) then I set up a pipeline:

tbs = (transform(b) for b in bs)  # or map(transform, bs)
z = {a: tb for tb in tbs for a in as_}

The first generator comprehension can be easily embedded in the other:

z = {a: tb for tb in (transform(b) for b in bs) for a in as_}

This makes it super-obvious that transform is called for each b, not for 
each (b, a) pair, it works today, and there's no assignment expression 
needed at all.

Assignment expressions should not be about adding yet a third way to 
solve a problem that already has a perfectly good solution! ("Expand to 
a loop statement" is not a *perfectly* good solution.) To showcase 
assignment expressions, we should be solving problems that don't have a 
good solution now.

I'm still not convinced that Neil's "given" example will even work (see 
below) but *if he is right* that it does, perhaps that's a good reason 
to prefer the simpler := assignment expression syntax, since we're 
less likely to use it in confusing ways.

> But it's really REALLY not obvious. 

But is it even legal?

As I understand it, "given" is an expression, not an addition to 
comprehension syntax. In that case, I don't think Neil's example will 
work at all, for reasons I've already stated.

If that's not the case, then until somebody tells me what this new 
comprehension syntax means, and what it looks like, I have no idea what 
is intended.

Which of these can we write, and what do they do?

    [expression given name=something for x in seq]

    [expression for x given name=something in seq]

    [expression for x in seq given name=something]

    [expression for x in seq if given name=something condition]

    [expression for x in seq if condition given name=something]


More information about the Python-ideas mailing list