I want to create code that initialize variable only when I really need it. But initializing in the regular way:
var = None
if var is None:
    var = factory()
var2 = var
Make too much noise in the code.
I tried to create fast solution but I feel there is better option. This is my solution that is fast but can't get parameters and use defaultdict for this.
def lazy_variable(factory):
    data = defaultdict(factory)
    return lambda: data['']
var = lazy_variable(a_factory)
var2 = var()
More questions:
EDIT:
Please consider performance. I know i can create a class that can have this behavior, but it slower then the simple solution and also the default dict solution.
trying some of the solutions:
define:
import cachetools.func
import random
@cachetools.func.lru_cache(None)
def factory(i):
    return random.random()
and run:
%%timeit
for i in xrange(100):
    q = factory(i)
    q = factory(i)
got:
100 loops, best of 3: 2.63 ms per loop
naive:
%%timeit
for i in xrange(100):
    a = None
    if a is None:
        a = random.random()
    q = a
    q = a
got:
The slowest run took 4.71 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 14.8 µs per loop
I'm not sure what was cached
defaultdict solution:
%%timeit
for i in xrange(100):
    a = lazy_variable(random.random)
    q = a()
    q = a()
got:
The slowest run took 4.11 times longer than the fastest. This could mean that an intermediate result is being cached.
10000 loops, best of 3: 76.3 µs per loop
Tnx!
If we're talking about instance variables, then yes - you can write your own wrapper and have it behave the way you want:
class LazyVar(object):
    def __init__(self, factory, *args, **kwargs):
        self.id = "__value_" + str(id(self))  # internal store
        self.factory = factory
        self.args = args
        self.kwargs = kwargs
    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            try:
                return getattr(instance, self.id)
            except AttributeError:
                value = self.factory(*self.args, **self.kwargs)
                setattr(instance, self.id, value)
                return value
def factory(name):
    print("Factory called, initializing: " + name)
    return name.upper()  # just for giggles
class TestClass(object):
    foo = LazyVar(factory, "foo")
    bar = LazyVar(factory, "bar")
You can test it as:
test = TestClass()
print("Foo will get initialized the moment we mention it")
print("Foo's value is:", test.foo)
print("It will also work for referencing, so even tho bar is not initialized...")
another_bar = test.bar
print("It gets initialized the moment we set its value to some other variable")
print("They, of course, have the same value: `{}` vs `{}`".format(test.bar, another_bar))
Which will print:
Foo will get initialized the moment we mention it Factory called, initializing: foo Foo's value is: FOO It will also work for referencing, so even tho bar is not initialized... Factory called, initializing: bar It gets initialized the moment we set its value to some other variable They, of course, have the same value: `BAR` vs `BAR`
Unfortunately, you cannot use the same trick for globally declared variables  as __get__() gets called only when accessed as instance vars.
If I understand you correctly then some of the functionality you are interested in is provided by functools.lru_cache:
import functools as ft
@ft.lru_cache(None)
def lazy():
    print("I'm working soo hard")
    return sum(range(1000))
lazy() # 1st time factory is called
# I'm working soo hard
# 499500
lazy() # afterwards cached result is used
# 499500
The decorated factory may also take parameters:
@ft.lru_cache(None)
def lazy_with_args(x):
    print("I'm working so hard")
    return sum((x+i)**2 for i in range(100))
lazy_with_args(3.4)
# I'm working so hard
# 363165.99999999994
lazy_with_args(3.4)
# 363165.99999999994
# new parametes, factory is used to compute new value
lazy_with_args(-1.2)
# I'm working so hard
# 316614.00000000006
lazy_with_args(-1.2)
# 316614.00000000006
# old value stays in cache
lazy_with_args(3.4)
# 363165.99999999994 
                        A simple container (but which needs the parentheses nevertheless) can be done e.g. like this:
class Container:
    UNDEF = object()
    def __init__(self, factory):
        self.data = Container.UNDEF
        self.factory = factory
    def __call__(self):
        if self.data is Container.UNDEF:
            self.data = self.factory()
        return self.data
# Test:
var = Container(lambda: 5)
print(var())
print(var())
                        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