3

I would like to implement a function f which returns two values x and y given an input w. This function first computes the value of x using w and then y using the computed valued of x. However, since the computation of y is expensive, I want the function to compute its value only when I need the result from the function.

That is, the following call to f will not execute the part of the function which computes y ...

x, _ = f(w)

... and the following call will execute it ...

x, y = f(w)

Is it possible to define the function in this way? Of course, a straightforward alternative is to pass an extra Boolean variable into f which determines whether y is to be computed or not but I'm wondering if there are other ways to do this. Thanks!

user1953384
  • 1,049
  • 2
  • 15
  • 29
  • 1
    As Marijn says, this isn't possible. Also, _ is actually a variable here: trying doing `print _` after that statement. – adpeace Jul 24 '14 at 12:21

2 Answers2

3

This is the perfect case for a generator, with which you can delay the execution till you want it.

def defer_huge_operation(w):
    ...
    yield x
    ...
    ...
    ...
    yield y

op = defer_huge_operation(w)
x = next(op)
...
...
y = next(op)

When you do next(op), it will yield the value of x and the control will be returned to the main program and when you do next(op) again, the execution will be resumed from the place you left off and the huge operation will be performed to give you y.

Note: Once you consumed the generator completely, further next calls to the generator will raise StopIteration exception. Even if you want to do the same operation again, you need to create a new generator object like this

op = defer_huge_operation(w)

If you want to get all the values yielded by the generator, just iterate it with list or tuple function, like this

returned_values_list = list(defer_huge_operation(w))
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • I normally think of generators as being for generating a sequence of data of the same type, rather than two parts of a related calculation. This requires the caller to know how many values to expect. Whilst it works, I'm not sure I'd consider it idiomatic. – adpeace Jul 24 '14 at 12:25
  • 1
    I think the egenerator idea is pretty nice, but why not just do 2 functions - you have the value needed to continue so the heavy function can be called conditionally. – gkusner Jul 24 '14 at 12:38
  • Thanks, that's very helpful! I didn't know about yield before. So I'm assuming it should be ok for me to repeatedly do a `op = defer_huge_operation(w)` followed by just a single `x = next(op)` as long as I only need `x`, and then finally add a `y = next(op)` after the `x = next(op)` when I do actually need the `y`. – user1953384 Jul 24 '14 at 12:51
  • @user1953384 Yup, that should be perfectly fine :) – thefourtheye Jul 24 '14 at 12:54
  • 1
    @user1953384 while it would work, I would say this is bad practice: its not readable and bug prone (what if you comment out the first `next(op)` later?). I would suggest you use two different functions, one for `x` and another for `y` or pass a boolean flag, or use the lazy props result I suggested. Those are much better in practice. – Iftah Jul 24 '14 at 13:47
2

Another option is to return an object with lazy properties from the function, so the results will evaluate only when you read them the first time.

ie.

result = f(w)
x = result.x  # only x is calculated now
y = result.y  # y is calculated only here
y2 = result.y  # y isn't calculated a second time
Community
  • 1
  • 1
Iftah
  • 9,512
  • 2
  • 33
  • 45