0

Given:

  1. A set of computations to calculate interdependent variables
  2. A set of desired outputs chosen from the variables

I would like to:

  1. Compute only the variables I need (lazy computation)
  2. Compute each variable at most once (caching)
  3. Get rid of the variables that are no longer needed in either the output or the computation of the remaining outputs (garbage collection)
  4. BONUS: Order the computations so that the biggest variables to be removed can be removed first, in order to reduce memory usage to the max

For example:

    a = 1
    b = 2
    c = b ** 10
    d = a + b

In this example:

  1. If a is the only required output, then b, c, and d never need to be computed
  2. If c and d are required, then b should only be calculated once
  3. If c and d are required, then a can be forgotten as soon as d has been computed
  4. Since a can be eventually garbage collected, we try to arrange for that ASAP and therefore compute d first (or maybe we should start with c if the ** operation will temporarily take up more memory? Not completely sure about this...)

When writing the computations as above, as a plain sequence of statements, properties 1 and 4 are not observed.

Meanwhile, it is possible to obtain properties 1 and 3 (missing 2 and 4) using @property:

    class DataGetter:
        @property
        def a(self): return 1
        @property
        def b(self): return 2
        @property
        def c(self): return self.b ** 10
        @property
        def d(self): return self.a + self.b

Likewise, it is possible to obtain properties 1 and 2 (missing 3 and 4) using @cached_property:

    class DataGetter:
        @cached_property
        def a(self): return 1
        @cached_property
        def b(self): return 2
        @cached_property
        def c(self): return self.b ** 10
        @cached_property
        def d(self): return self.a + self.b

Is there a way to ensure all the first 3 properties are met? (And possibly also the 4th?)

1 Answer 1

2

If we wrap each variable in an instance, then laziness can be achieved by deferring the computation with lambda:, and caching can be achieved in the normal way. Since the lambdas are closures, each will hold "cells" which retain only the local variables from the outer function that the lambda actually uses (see this Q&A), allowing garbage collection to work as desired.

class LazyValue:
    def __init__(self, f):
        self._f = f
    @cached_property
    def value(self):
        v = self._f()
        self._f = None
        return v

Setting self._f = None is required for garbage collection to work as desired; if a value has already been computed then we don't need or want to retain references to any other LazyValue instances which self._f closes over.

Usage:

def compute():
    a = LazyValue(lambda: 1)
    b = LazyValue(lambda: 2)
    c = LazyValue(lambda: b.value ** 10)
    d = LazyValue(lambda: a.value + b.value)
    # return whichever results are required
    return d

print(compute().value) # 3
Sign up to request clarification or add additional context in comments.

3 Comments

Ingenious. How would you usually return the values? Say you have a list of variable names as input, would you return something like this? {k:v.value for k,v in [('a',a),('b',b),('c',c),('d',d),] if k in required_outputs}
P.S. it looks like LazyValue's value could simply be a @cached_property instead of a @property mimicking the behavior of @cached_property. Any reason behind that? Compatibility?
@LemmeTestThat For returning values, it actually makes more sense to return the unevaluated variables rather than evaluating them first; otherwise there is no possible garbage collection because you're evaluating them while they're all referenced by local variables anyway. To select some variables by name, you can use locals() to avoid repetition. Good point about @cached_property. Also, I've made an edit regarding garbage collection because I realised self._f would still retain references after it was no longer needed.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.