2

One of the examples given in PEP572 is

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

currently in python, you'd have to do one of the following:

# option 1
y = f(x)
[y, y**2, y**3]

or

# option 2 
[f(x), f(x)**2, f(x)**3]

the example implies that option 2 here could be improved, but I have never seen that recommended over the first option. Is there ever a reason why option 2 (and therefore the walrus operator) would be better than option 1?

sophros
  • 14,672
  • 11
  • 46
  • 75
ChrisW
  • 4,970
  • 7
  • 55
  • 92
  • 2
    Could you label more clearly which options you are referring to? – norok2 Aug 01 '19 at 12:32
  • The walrus operator is basically a flavor of the assignment operator that can be used in contexts where the usage of the latter is not allowed by the grammar, so the first two options should be equivalent. – ForceBru Aug 01 '19 at 12:34
  • 2
    Anyway `y = f(x); [y, y**2, y**3]` and `[f(x), f(x)**2, f(x)**3]` are not doing the same thing, in general, and `[y := f(x), y**2, y**3]` is only equivalent to the two-line code. – norok2 Aug 01 '19 at 12:35

2 Answers2

7

Just to make things clear:

[y := f(x), y**2, y**3]

is equivalent to:

y = f(x)
[y, y**2, y**3]

(f(x) is called only once)

but, in general, not this:

[f(x), f(x)**2, f(x)**3]

(f(x) is called three times)

because of potential f() side-effects (or potential unnecessary computational burden, if f() is a pure function).

So, in general, replacing [f(x), f(x)**2, f(x)**3] with [y := f(x), y**2, y**3] should be inspected carefully.


For example:

def f(x):
    print('Brooks was here.')
    return 2 * x


x = 1
y = f(x)
l1 = [y, y**2, y**3]

prints Brooks was here. once, while:

l2 = [f(x), f(x)**2, f(x)**3]

will print Brooks was here. three times. Of course, l1 == l2.


So, to answer your question more directly, you may want to use:

[f(x), f(x)**2, f(x)**3]

and not this

y = f(x)
[y, y**2, y**3]

when you are specifically interested in the side-effects, whatever that might be.

norok2
  • 25,683
  • 4
  • 73
  • 99
  • 2
    +In cases where subsequent `f(x)` give the same result and there are no side effects, second option allows us to use list comprehensions instead of loops. However, if `f(x)` takes long/much memory to calculate, we'd have to use loop with 1st option... or keep the list comprehension with the new walrus operator, so we will calculate `f(x)` only once. – h4z3 Aug 01 '19 at 12:45
  • 1
    @h4z3 I was updating precisely that while you were commenting – norok2 Aug 01 '19 at 12:47
  • This makes sense superficially, but I don't see why the PEP example explicitly says "# Reuse a value that's expensive to compute", if it's replacing an example where `f(x)` is only being computed once? – ChrisW Aug 01 '19 at 13:36
  • @ChrisW you have to realize that in this case it is only syntactic sugar (coffee?) – norok2 Aug 01 '19 at 14:09
  • @norok2 ah, I guess I hadn't thought of the example where the function has side effects – ChrisW Aug 09 '19 at 15:34
  • https://www.python.org/dev/peps/pep-0572/#rationale `[Naming the result of an expression] is available only in statement form, making it unavailable in list comprehensions and other expression contexts. Additionally, naming sub-parts of a large expression can assist an interactive debugger, providing useful display hooks and partial results.` – Dorian Turba Feb 05 '20 at 16:22
1

A walrus with dynamic programming, it could be faster depend on f(x).

e = 1
[e := e * f(x) for i in range(1, 4)]
Ke Zhang
  • 937
  • 1
  • 10
  • 24