Im curious to know how lazy evaluation is implemented at higher levels, ie in libraries, etc. For example, how does the Django ORM or ActiveRecord defer evaluation of query until it is actually used?
Let's have a look at some methods for django's django.db.models.query.QuerySet class:
class QuerySet(object):
"""
Represents a lazy database lookup for a set of objects.
"""
def __init__(self, model=None, query=None, using=None):
...
self._result_cache = None
...
def __len__(self):
if self._result_cache is None:
...
elif self._iter:
...
return len(self._result_cache)
def __iter__(self):
if self._result_cache is None:
...
if self._iter:
...
return iter(self._result_cache)
def __nonzero__(self):
if self._result_cache is not None:
...
def __contains__(self, val):
if self._result_cache is not None:
...
else:
...
...
def __getitem__(self, k):
...
if self._result_cache is not None:
...
...
The pattern that these methods follow is that no queries are executed until some method that really needs to return some result is called. At that point, the result is stored in self._result_cache and any subsequent call to the same method returns the cached value.
In Python, one object may "exist" - but its intrinsic value will only be known by the outer world at the moment it is used with one of the operators - since the operators are defined in the class by the magic names with double underscores, if a class writes the appropriate code to execute the deferred code when the operator is called, it is just fine.
That means, if the object's value is, for example, to be used like a string, any part of the program that will use the object will call, at some point, the "__str__" coercion method.
For example, let's create an object that behaves like a string, but tells the current time. Strings can be concatenated to other strings(__add__), can have their length requested (__len__), and so on. If we want it to fit perfectly in the place of a string, we'd have to override all methods. The idea is to retrieve the actual value just when one of the operators is called - otherwise, the actual object can freely be assigned to variables, and passed around. It will only be evaluated when its value is needed
Then, one can have some code like this:
class timestr(object):
def __init__(self):
self.value = None
def __str__(self):
self._getvalue()
return self.value
def __len__(self):
self._getvalue()
return len(self.value)
def __add__(self, other):
self._getvalue()
return self.value + other
def _getvalue(self):
timet = time.localtime()
self.value = " %s:%s:%s " % (timet.tm_hour, timet.tm_min, timet.tm_sec)
And using it on the console, you may have:
>>> a = timestr()
>>> b = timestr()
>>> print b
17:16:22
>>> print a
17:16:25
If the value for which you want a lazy evaluation is an attribute of your object (like Peson.name ) instead of what your object actually behaves like - it is even easier. Because Python allows all object attributes to be of a special type - called a descriptor -- which actually has a method called each time the attribute will be accessed. Therefore, one just has to create a class with a proper method named __get__ to fetch the actual value. This method will be called only when the attribute is needed.
Python even has an utility for easy descriptor creation - the "property" keyword, that makes this even easier - you pass a method that is the code to generate the attribute as the first parameter to property.
So, having an Event class with a lazy (and live) evaluated time, is just a matter of writting:
import time
class Event(object):
#property
def time(self):
timet = time.localtime()
return " %s:%s:%s " % (timet.tm_hour, timet.tm_min, timet.tm_sec)
And use it as in:
>>> e= Event()
>>> e.time
' 17:25:8 '
>>> e.time
' 17:25:10 '
The mechanism is quite simple:
class Lazy:
def __init__(self, evaluate):
self.evaluate = evaluate
self.computed = False
def getresult(self):
if not self.computed:
self.result = self.evaluate()
self.computed = True
return self.result
Then, this utility can be used as:
def some_computation(a, b):
return ...
# bind the computation to its operands, but don't evaluate it yet.
lazy = Lazy(lambda: some_computation(1, 2))
# "some_computation()" is evaluated now.
print lazy.getresult()
# use the cached result again without re-computing.
print lazy.getresult()
This implementation uses callables to represent the computation, but there are many variations on this theme (e.g. a base class that requires you to imlement an evaluate() method, etc.).
Not sure about the specifics about which library you talking about but, from an algorithm standpoint, I've always used/undertsood it as follows: (psuedo code from a python novice)
class Object:
#... Other stuff ...
_actual_property = None;
def interface():
if _actual_property is None:
# Execute query and load up _actual_property
return _actual_property
Essentially because the interface and implementation are separated, you can define behaviors to execute upon request.
Related
I created a mutable String class in Python, based on the builtin str class.
I can change the first character, but when I call capitalize(), it uses the old value instead
class String(str):
def __init__(self, string):
self.string = list(string)
def __repr__(self):
return "".join(self.string)
def __str__(self):
return "".join(self.string)
def __setitem__(self, index, value):
self.string[index] = value
def __getitem__(self, index):
if type(index) == slice:
return "".join(self.string[index])
return self.string[index]
def __delitem__(self, index):
del self.string[index]
def __add__(self, other_string):
return String("".join(self.string) + other_string)
def __len__(self):
return len(self.string)
text = String("cello world")
text[0] = "h"
print(text)
print(text.capitalize())
Expected Output :
hello world
Hello world
Actual Output :
hello world
Cello world
Your implementation inherits from str, so it brings along all the methods that str implements. However, the implementation of the str.capitalize() method is not designed to take that into account. Methods like str.capitalize() return a new str object with the required change applied.
Moreover, the Python built-in types do not store their state in a __dict__ mapping of attributes, but use internal struct data structures) only accessible on the C level; your self.string attribute is not where the (C equivalent of) str.__new__() stores the string data. The str.capitalize() method bases its return value on the value stored in the internal data structure when the instance was created, which can't be altered from Python code.
You'll have to shadow all the str methods that return a new value, including str.capitalize() to behave differently. If you want those methods from returning a new instance to changing the value in-place, you have to do so yourself:
class String(str):
# ...
def capitalize(self):
"""Capitalize the string, in place"""
self.string[:] ''.join(self.string).capitalize()
return self # or return None, like other mutable types would do
That can be a lot of work, writing methods like these for every possible str method that returns an updated value. Instead, you could use a __getattribute__ hook to redirect methods:
_MUTATORS = {'capitalize', 'lower', 'upper', 'replace'} # add as needed
class String(str):
# ...
def __getattribute__(self, name):
if name in _MUTATORS:
def mutator(*args, **kwargs):
orig = getattr(''.join(self.string), name)
self.string[:] = orig(*args, **kwargs)
return self # or return None for Python type consistency
mutator.__name__ = name
return mutator
return super().__getattribute__(name)
Demo with the __getattribute__ method above added to your class:
>>> text = String("cello world")
>>> text[0] = "h"
>>> print(text)
hello world
>>> print(text.capitalize())
Hello world
>>> print(text)
Hello world
One side note: the __repr__ method should use repr() to return a proper representation, not just the value:
def __repr__(self):
return repr(''.join(self.string))
Also, take into account that most Python APIs that are coded in C and take a str value as input, are likely to use the C API for Unicode strings and so not only completely ignore your custom implementations but like the original str.capitalize() method will also ignore the self.string attribute. Instead, they too will interact with the internal str data.
This approach is inferior to the already suggested answers. There is more overhead because you don't get to just track things as a list, and isinstance(s, str) won't work, for example.
Another way to accomplish this is to subclass collections.UserString. It's a wrapper around the built-in string type that stores it as a member named data. So you could do something like
from collections import UserString
class String(UserString):
def __init__(self, string):
super().__init__(string)
def __setitem__(self, index, value):
data_list = list(self.data)
data_list[index] = value
self.data = "".join(data_list)
# etc.
And then you will get capitalize and the other string methods for free.
You inherited str's definition of capitalize, which ignores your class's behaviors and just uses the underlying data of the "real" str.
Inheriting from a built-in type like this effectively requires you to reimplement every method, or do some metaprogramming with __getattribute__; otherwise, the underlying type's behaviors will be inherited unmodified.
I have a custom class like the below. The idea, as the naming suggests, is that I want to evaluate a token stream in a parser-type tool. Once a bunch of constructs have been parsed out and put into data structures, certain sequences of tokens will evaluate to an int, but when the data structures aren't available yet, a function just returns None instead. This single complex data structure 'constructs' gets passed around pretty much everywhere in the program.
class Toks(list):
def __init__(self, seq, constructs=Constructs()):
list.__init__(self, seq)
self.constructs = constructs
#property
def as_value(self):
val = tokens_as_value(self, self.constructs)
return val if val is not None else self
At points in the code, I want to assign this maybe-computable value to a name, e.g.:
mything.val = Toks(tokens[start:end], constructs).as_value
Well, this gives mything.val either an actual int value or a funny thing that allows us to compute a value later. But this requires a later pass to actually perform the computation, similar to:
if not isinstance(mything.val, int):
mything.val = mything.val.as_value
As it happens, I can do this in my program. However, what I'd really like to happen is to avoid the second pass altogether, and just have access to the property perform the computation and give the computed value if it's computable at that point (and perhaps evaluate to some sentinal if it's not possible to compute).
Any ideas?
To clarify: Depending on the case I get "value" differently; actual code is more like:
if tok.type == 'NUMBER':
mything.val = tok.value # A simple integer value
else:
mything.val = Toks(tokens[start:end], constructs).as_value
There are additional cases, sometimes I know I know the actual value early, and sometimes I'm not sure if I'll only know it later.
I realize I can defer calling (a bit more compactly than #dana suggests) with:
return val if val is not None else lambda: self.as_value
However, that makes later access inconsistent between mything.val and mything.val(), so I'd still have to guard it with an if to see which style to use. It's the same inconvenience whether I need to fall back to mything.val.as_value or to mything.val() after the type check.
You could easily do something like:
class NaiveLazy(object):
def __init__(self, ctor):
self.ctor = ctor
self._value = None
#property
def value(self):
if self._value is None:
self._value = ctor()
return self._value
mything = NaiveLazy(lambda: time.sleep(5) and 10)
And then always use mything.value (example to demonstrate evaluation):
print mything.value # will wait 5 seconds and print 10
print mything.value # will print 10
I've seen some utility libraries create a special object for undefined in case ctor returns None. If you eventually want to extend your code beyond ints, you should think about that:
class Undefined(object): pass
UNDEFINED = Undefined()
#...
self._value = UNDEFINED
#...
if self._value is UNDEFINED: self._value = ctor()
For your example specifically:
def toks_ctor(seq, constructs=Constructs()):
return lambda l=list(seq): tokens_as_value(l, constructs) or UNDEFINED
mything = NaiveLazy(toks_ctor(tokens[start:end], constructs))
If you're using Python3.2+, consider a Future object. This tool lets you run any number of calculations in the background. You can wait for a single future to be completed, and use its value. Or, you can "stream" the results one at a time as they're completed.
You could return a callable object from as_value, which would allow you automatically check for the real return value automatically. The one drawback is you'd need to use mything.val() instead of mything.val:
def tokens_as_value(toks, constructs):
if constructs.constructed:
return "some value"
else:
return None
class Constructs(object):
def __init__(self):
self.constructed = False
class Toks(list):
def __init__(self, seq, constructs=Constructs()):
list.__init__(self, seq)
self.constructs = constructs
#property
def as_value(self):
return FutureVal(tokens_as_value, self, self.constructs)
class FutureVal(object):
def __init__(self, func, *args, **kwargs):
self.func = func
self._val = None
self.args = args
self.kwargs = kwargs
def __call__(self):
if self._val is None:
self._val = self.func(*self.args, **self.kwargs)
return self._val
Just for the purposes of the example, Constructs just contains a boolean that indicates whether or not a real value should be returned from tokens_as_value.
Usage:
>>> t = test.Toks([])
>>> z = t.as_value
>>> z
<test.FutureVal object at 0x7f7292c96150>
>>> print(z())
None
>>> t.constructs.constructed = True
>>> print(z())
our value
I find it very interesting the way how SQLAlchemy constructing query strings, eg:
(Session.query(model.User)
.filter(model.User.age > 18)
.order_by(model.User.age)
.all())
As far as I can see, there applied some kind of Proxy Pattern. In my small project I need to make similar string construction using OOP approach. So, I tried to reconstitute this behavior.
Firstly, some kind of object, one of plenty similar objects:
class SomeObject(object):
items = None
def __init__(self):
self.items = []
def __call__(self):
return ' '.join(self.items) if self.items is not None else ''
def a(self):
self.items.append('a')
return self
def b(self):
self.items.append('b')
return self
All methods of this object return self, so I can call them in any order and unlimited number of times.
Secondly, proxy object, that will call subject's methods if it's not a perform method, which calls object to see the resulting string.
import operator
class Proxy(object):
def __init__(self, some_object):
self.some_object = some_object
def __getattr__(self, name):
self.method = operator.methodcaller(name)
return self
def __call__(self, *args, **kw):
self.some_object = self.method(self.some_object, *args, **kw)
return self
def perform(self):
return self.some_object()
And finally:
>>> obj = SomeObject()
>>> p = Proxy(obj)
>>> print p.a().a().b().perform()
a a b
What can you say about this implementation? Is there better ways to make the desirable amount of classes that would make such a string cunstructing with the same syntax?
PS: Sorry for my english, it's not my primary language.
Actually what you are looking at is not a proxy pattern but the builder pattern, and yes your implementation is IMHO is the classic one (using the Fluent interface pattern).
I don't know what SQLAlchemy does, but I would implement the interface by having the Session.query() method return a Query object with methods like filter(), order_by(), all() etc. Each of these methods simply returns a new Query object taking into account the applied changes. This allows for method chaining as in your first example.
Your own code example has numerous problems. One example
obj = SomeObject()
p = Proxy(obj)
a = p.a
b = p.b
print a().perform() # prints b
I don't know if this will make sense, but...
I'm trying to dynamically assign methods to an object.
#translate this
object.key(value)
#into this
object.method({key:value})
To be more specific in my example, I have an object (which I didn't write), lets call it motor, which has some generic methods set, status and a few others. Some take a dictionary as an argument and some take a list. To change the motor's speed, and see the result, I use:
motor.set({'move_at':10})
print motor.status('velocity')
The motor object, then formats this request into a JSON-RPC string, and sends it to an IO daemon. The python motor object doesn't care what the arguments are, it just handles JSON formatting and sockets. The strings move_at and velocity are just two of what might be hundreds of valid arguments.
What I'd like to do is the following instead:
motor.move_at(10)
print motor.velocity()
I'd like to do it in a generic way since I have so many different arguments I can pass. What I don't want to do is this:
# create a new function for every possible argument
def move_at(self,x)
return self.set({'move_at':x})
def velocity(self)
return self.status('velocity')
#and a hundred more...
I did some searching on this which suggested the solution lies with lambdas and meta programming, two subjects I haven't been able to get my head around.
UPDATE:
Based on the code from user470379 I've come up with the following...
# This is what I have now....
class Motor(object):
def set(self,a_dict):
print "Setting a value", a_dict
def status(self,a_list):
print "requesting the status of", a_list
return 10
# Now to extend it....
class MyMotor(Motor):
def __getattr__(self,name):
def special_fn(*value):
# What we return depends on how many arguments there are.
if len(value) == 0: return self.status((name))
if len(value) == 1: return self.set({name:value[0]})
return special_fn
def __setattr__(self,attr,value): # This is based on some other answers
self.set({attr:value})
x = MyMotor()
x.move_at = 20 # Uses __setattr__
x.move_at(10) # May remove this style from __getattr__ to simplify code.
print x.velocity()
output:
Setting a value {'move_at': 20}
Setting a value {'move_at': 10}
10
Thank you to everyone who helped!
What about creating your own __getattr__ for the class that returns a function created on the fly? IIRC, there's some tricky cases to watch out for between __getattr__ and __getattribute__ that I don't recall off the top of my head, I'm sure someone will post a comment to remind me:
def __getattr__(self, name):
def set_fn(self, value):
return self.set({name:value})
return set_fn
Then what should happen is that calling an attribute that doesn't exist (ie: move_at) will call the __getattr__ function and create a new function that will be returned (set_fn above). The name variable of that function will be bound to the name parameter passed into __getattr__ ("move_at" in this case). Then that new function will be called with the arguments you passed (10 in this case).
Edit
A more concise version using lambdas (untested):
def __getattr__(self, name):
return lambda value: self.set({name:value})
There are a lot of different potential answers to this, but many of them will probably involve subclassing the object and/or writing or overriding the __getattr__ function.
Essentially, the __getattr__ function is called whenever python can't find an attribute in the usual way.
Assuming you can subclass your object, here's a simple example of what you might do (it's a bit clumsy but it's a start):
class foo(object):
def __init__(self):
print "initting " + repr(self)
self.a = 5
def meth(self):
print self.a
class newfoo(foo):
def __init__(self):
super(newfoo, self).__init__()
def meth2(): # Or, use a lambda: ...
print "meth2: " + str(self.a) # but you don't have to
self.methdict = { "meth2":meth2 }
def __getattr__(self, name):
return self.methdict[name]
f = foo()
g = newfoo()
f.meth()
g.meth()
g.meth2()
Output:
initting <__main__.foo object at 0xb7701e4c>
initting <__main__.newfoo object at 0xb7701e8c>
5
5
meth2: 5
You seem to have certain "properties" of your object that can be set by
obj.set({"name": value})
and queried by
obj.status("name")
A common way to go in Python is to map this behaviour to what looks like simple attribute access. So we write
obj.name = value
to set the property, and we simply use
obj.name
to query it. This can easily be implemented using the __getattr__() and __setattr__() special methods:
class MyMotor(Motor):
def __init__(self, *args, **kw):
self._init_flag = True
Motor.__init__(self, *args, **kw)
self._init_flag = False
def __getattr__(self, name):
return self.status(name)
def __setattr__(self, name, value):
if self._init_flag or hasattr(self, name):
return Motor.__setattr__(self, name, value)
return self.set({name: value})
Note that this code disallows the dynamic creation of new "real" attributes of Motor instances after the initialisation. If this is needed, corresponding exceptions could be added to the __setattr__() implementation.
Instead of setting with function-call syntax, consider using assignment (with =). Similarly, just use attribute syntax to get a value, instead of function-call syntax. Then you can use __getattr__ and __setattr__:
class OtherType(object): # this is the one you didn't write
# dummy implementations for the example:
def set(self, D):
print "setting", D
def status(self, key):
return "<value of %s>" % key
class Blah(object):
def __init__(self, parent):
object.__setattr__(self, "_parent", parent)
def __getattr__(self, attr):
return self._parent.status(attr)
def __setattr__(self, attr, value):
self._parent.set({attr: value})
obj = Blah(OtherType())
obj.velocity = 42 # prints setting {'velocity': 42}
print obj.velocity # prints <value of velocity>
Recently I've gone through an existing code base containing many classes where instance attributes reflect values stored in a database. I've refactored a lot of these attributes to have their database lookups be deferred, ie. not be initialised in the constructor but only upon first read. These attributes do not change over the lifetime of the instance, but they're a real bottleneck to calculate that first time and only really accessed for special cases. Hence they can also be cached after they've been retrieved from the database (this therefore fits the definition of memoisation where the input is simply "no input").
I find myself typing the following snippet of code over and over again for various attributes across various classes:
class testA(object):
def __init__(self):
self._a = None
self._b = None
#property
def a(self):
if self._a is None:
# Calculate the attribute now
self._a = 7
return self._a
#property
def b(self):
#etc
Is there an existing decorator to do this already in Python that I'm simply unaware of? Or, is there a reasonably simple way to define a decorator that does this?
I'm working under Python 2.5, but 2.6 answers might still be interesting if they are significantly different.
Note
This question was asked before Python included a lot of ready-made decorators for this. I have updated it only to correct terminology.
Here is an example implementation of a lazy property decorator:
import functools
def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
#property
#functools.wraps(fn)
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
class Test(object):
#lazyprop
def a(self):
print 'generating "a"'
return range(5)
Interactive session:
>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]
I wrote this one for myself... To be used for true one-time calculated lazy properties. I like it because it avoids sticking extra attributes on objects, and once activated does not waste time checking for attribute presence, etc.:
import functools
class lazy_property(object):
'''
meant to be used for lazy evaluation of an object attribute.
property should represent non-mutable data, as it replaces itself.
'''
def __init__(self, fget):
self.fget = fget
# copy the getter function's docstring and other attributes
functools.update_wrapper(self, fget)
def __get__(self, obj, cls):
if obj is None:
return self
value = self.fget(obj)
setattr(obj, self.fget.__name__, value)
return value
class Test(object):
#lazy_property
def results(self):
calcs = 1 # Do a lot of calculation here
return calcs
Note: The lazy_property class is a non-data descriptor, which means it is read-only. Adding a __set__ method would prevent it from working correctly.
For all sorts of great utilities I'm using boltons.
As part of that library you have cachedproperty:
from boltons.cacheutils import cachedproperty
class Foo(object):
def __init__(self):
self.value = 4
#cachedproperty
def cached_prop(self):
self.value += 1
return self.value
f = Foo()
print(f.value) # initial value
print(f.cached_prop) # cached property is calculated
f.value = 1
print(f.cached_prop) # same value for the cached property - it isn't calculated again
print(f.value) # the backing value is different (it's essentially unrelated value)
property is a class. A descriptor to be exact. Simply derive from it and implement the desired behavior.
class lazyproperty(property):
....
class testA(object):
....
a = lazyproperty('_a')
b = lazyproperty('_b')
Here's a callable that takes an optional timeout argument, in the __call__ you could also copy over the __name__, __doc__, __module__ from func's namespace:
import time
class Lazyproperty(object):
def __init__(self, timeout=None):
self.timeout = timeout
self._cache = {}
def __call__(self, func):
self.func = func
return self
def __get__(self, obj, objcls):
if obj not in self._cache or \
(self.timeout and time.time() - self._cache[key][1] > self.timeout):
self._cache[obj] = (self.func(obj), time.time())
return self._cache[obj]
ex:
class Foo(object):
#Lazyproperty(10)
def bar(self):
print('calculating')
return 'bar'
>>> x = Foo()
>>> print(x.bar)
calculating
bar
>>> print(x.bar)
bar
...(waiting 10 seconds)...
>>> print(x.bar)
calculating
bar
What you really want is the reify (source linked!) decorator from Pyramid:
Use as a class method decorator. It operates almost exactly like the Python #property decorator, but it puts the result of the method it decorates into the instance dict after the first call, effectively replacing the function it decorates with an instance variable. It is, in Python parlance, a non-data descriptor. The following is an example and its usage:
>>> from pyramid.decorator import reify
>>> class Foo(object):
... #reify
... def jammy(self):
... print('jammy called')
... return 1
>>> f = Foo()
>>> v = f.jammy
jammy called
>>> print(v)
1
>>> f.jammy
1
>>> # jammy func not called the second time; it replaced itself with 1
>>> # Note: reassignment is possible
>>> f.jammy = 2
>>> f.jammy
2
They added exactly what you're looking for in python 3.8
Transform a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance.
Similar to property(), with the addition of caching.
Use it just like #property :
#cached_property
def a(self):
self._a = 7
return self._a
There is a mix up of terms and/or confusion of concepts both in question and in answers so far.
Lazy evaluation just means that something is evaluated at runtime at the last possible moment when a value is needed. The standard #property decorator does just that.(*) The decorated function is evaluated only and every time you need the value of that property. (see wikipedia article about lazy evaluation)
(*)Actually a true lazy evaluation (compare e.g. haskell) is very hard to achieve in python (and results in code which is far from idiomatic).
Memoization is the correct term for what the asker seems to be looking for. Pure functions that do not depend on side effects for return value evaluation can be safely memoized and there is actually a decorator in functools #functools.lru_cache so no need for writing own decorators unless you need specialized behavior.
You can do this nice and easily by building a class from Python native property:
class cached_property(property):
def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __set__(self, obj, value):
obj.__dict__[self.__name__] = value
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, None)
if value is None:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
We can use this property class like regular class property ( It's also support item assignment as you can see)
class SampleClass():
#cached_property
def cached_property(self):
print('I am calculating value')
return 'My calculated value'
c = SampleClass()
print(c.cached_property)
print(c.cached_property)
c.cached_property = 2
print(c.cached_property)
print(c.cached_property)
Value only calculated first time and after that we used our saved value
Output:
I am calculating value
My calculated value
My calculated value
2
2
I agree with #jason
When I think about lazy evaluation, Asyncio immediately comes to mind.
The possibility of delaying the expensive calculation till the last minute is the sole benefit of lazy evaluation.
Caching / memozition on the other hand could be beneficial but on the expense that the calculation is static and won't change with time / inputs.
A practice I often do for expensive calculations of these sorts is to calculate then cache with TTL.