I'm currently trying to port some Scala code to a Python project and I came across the following bit of Scala code:
  lazy val numNonZero = weights.filter { case (k,w) => w > 0 }.keys
weights is a really long list of tuples of items and their associated probability weighting. Elements are frequently added and removed from this list but checking how many elements have a non-zero probability is relatively rare. There are a few other rare-but-expensive operations like this in the code I'm porting that seem to benefit greatly from usage of lazy val. What is the most idiomatic Python way to do something similar to Scala's lazy val?
In Scala, lazy val is a final variable that is evaluated once at the time it is first accessed, rather than at the time it is declared.
It is essentially a memoized function with no arguments.
Here's one way you can implement a memoization decorator in Python:
from functools import wraps
def memoize(f):
    @wraps(f)
    def memoized(*args, **kwargs):
        key = (args, tuple(sorted(kwargs.items()))) # make args hashable
        result = memoized._cache.get(key, None)
        if result is None:
            result = f(*args, **kwargs)
            memoized._cache[key] = result
        return result
    memoized._cache = {}
    return memoized
Here's how it can be used. With property you can even drop the empty parentheses, just like Scala:
>>> class Foo:
...     @property
...     @memoize
...     def my_lazy_val(self):
...         print "calculating"
...         return "some expensive value"
>>> a = Foo()
>>> a.my_lazy_val
calculating
'some expensive value'
>>> a.my_lazy_val
'some expensive value'
                        You can use @functools.lru_cache(maxsize=None) on a nullary function to emulate a lazy val.
Python 3.6.5 (default, Mar 30 2018, 06:41:53) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import functools
>>> import random
>>> def foo():
...     @functools.lru_cache(maxsize=None)
...     def bar():
...         return random.random()
...     return bar
... 
>>> f1 = foo()
>>> f2 = foo()
>>> f1()
0.11043217592970156
>>> f1()
0.11043217592970156
>>> f2()
0.3545457696543922
>>> f2()
0.3545457696543922
                        Essentially, you want to change how attribute access works for numNonZero. Python does that with a descriptor. In particular, take a look at their application to Properties.
With that, you can defer calculation until the attribute is accessed, caching it for later use.
Generator expression
>>> weights = [(1,2), (2,0), (3, 1)]
>>> numNonZero = (k for k, w in weights if w > 0)
>>> next(numNonZero)
1
>>> next(numNonZero)
3
>>> next(numNonZero)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> next(numNonZero, -1)
-1
>>> numNonZero = (k for k, w in weights if w > 0)
>>> for k in numNonZero:
...     print(k)
... 
1
3
Python tutorial: Generator expressions
A simpler variant of @sam-thomson's approach (inspired by his approach):
In [1]:     class Foo:
   ...:         def __init__(self):
   ...:             self.cached_lazy_val=None
   ...:         @property
   ...:         def my_lazy_val(self):
   ...:             if not self.cached_lazy_val:
   ...:                 print("calculating")
   ...:                 self.cached_lazy_val='some expensive value'
   ...:             return self.cached_lazy_val
   ...:
In [2]: f=Foo()
In [3]: f.my_lazy_val
calculating
Out[3]: 'some expensive value'
In [4]: f.my_lazy_val
Out[4]: 'some expensive value'
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With