有没有更好的方法来创建惰性变量初始化?

Is there better way to create lazy variable initialization?

我想创建仅在真正需要时才初始化变量的代码。但以常规方式初始化:

var = None

if var is None:
    var = factory()
var2 = var

在代码中制造太多噪音。

我尝试创建快速解决方案,但我觉得有更好的选择。这是我的解决方案,速度很快但无法获取参数并为此使用 defaultdict。

def lazy_variable(factory):
    data = defaultdict(factory)
    return lambda: data['']

var = lazy_variable(a_factory)
var2 = var()

更多问题:

编辑:

请考虑性能。我知道我可以创建一个可以有这种行为的 class,但它比简单的解决方案和默认的 dict 解决方案慢。

尝试一些解决方案:

定义:

import cachetools.func
import random

@cachetools.func.lru_cache(None)
def factory(i):
    return random.random()

和运行:

%%timeit

for i in xrange(100):
    q = factory(i)
    q = factory(i)

得到:

100 loops, best of 3: 2.63 ms per loop

幼稚:

%%timeit

for i in xrange(100):
    a = None
    if a is None:
        a = random.random()
    q = a
    q = a

得到:

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

我不确定缓存了什么

defaultdict解决方案:

%%timeit

for i in xrange(100):
    a = lazy_variable(random.random)
    q = a()
    q = a()

得到:

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

谢谢!

一个简单的容器(但仍然需要括号)可以完成,例如像这样:

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())

如果我们谈论的是实例变量,那么是的 - 您可以编写自己的包装器并让它按照您想要的方式运行:

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")

您可以将其测试为:

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))

将打印:

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`

不幸的是,您不能对全局声明的变量使用相同的技巧,因为 __get__() 仅在作为实例变量访问时才被调用。

好吧,你 可以 只需访问 locals()globals() 并键入

var2 = locals().get('var', factory())

但我从来没有遇到过这样有用的情况,所以你可能应该评估一下为什么你想做你想做的事。

如果我理解正确,那么您感兴趣的一些功能由 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

装饰工厂还可以带参数:

@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 

好的,我想我找到了一个使用生成器的快速解决方案:

def create_and_generate(creator):
    value = creator()
    while True:
        yield value    


def lazy_variable(creator):
    generator_instance = create_and_generate(creator)
    return lambda: next(generator_instance)

另一个快速解决方案是:

def lazy_variable(factory):
    data = []
    def f():
        if not data:
            data.extend((factory(),))
        return data[0]
    return f

但我认为生成器更清晰。