如何实现延迟评估(例如在ORM中)

时间:2012-01-23 17:42:43

标签: python activerecord orm

我很想知道如何在更高级别(即库等)中实现延迟评估。例如,Django ORM或ActiveRecord如何推迟对查询的评估,直到实际使用它为止?

4 个答案:

答案 0 :(得分:7)

让我们来看看django的django.db.models.query.QuerySet类的一些方法:

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:
        ...
        ...

这些方法遵循的模式是,在调用真正需要返回某些结果的某个方法之前,不会执行任何查询。此时,结果存储在self._result_cache中,对同一方法的任何后续调用都会返回缓存的值。

答案 1 :(得分:5)

在Python中,一个对象可能“存在” - 但它的内在价值只有在与其中一个运算符一起使用时才被外部世界所知 - 因为运算符是在类中由魔术名称定义的双下划线,如果一个类在调用运算符时写入适当的代码来执行延迟代码,那就没关系了。

这意味着,如果对象的值例如像字符串一样使用,那么将使用该对象的程序的任何部分都会调用“__str__”强制方法。

例如,让我们创建一个行为类似字符串的对象,但告诉当前时间。字符串可以连接到其他字符串(__ add__),可以请求它们的长度(__len__),依此类推。如果我们希望它完全适合字符串的位置,我们必须覆盖所有方法。这个想法是在调用其中一个运算符时检索实际值 - 否则,实际对象可以自由地分配给变量并传递。只有在需要其值时才会对其进行评估

然后,可以有一些像这样的代码:

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)

在控制台上使用它,你可能有:

>>> a = timestr()
>>> b = timestr()
>>> print b
 17:16:22 
>>> print a
 17:16:25 

如果您想要进行延迟评估的值是对象的属性(如Peson.name)而不是对象的实际行为,则更容易。因为Python允许所有对象属性都是特殊类型 - 称为描述符 - 实际上每次访问属性时都会调用一个方法。因此,只需要使用名为__get__的适当方法创建一个类来获取实际值。只有在需要属性时才会调用此方法。

Python甚至还有一个用于轻松创建描述符的实用程序 - “property”关键字,这使得这更容易 - 您传递一个方法,即生成属性的代码作为属性的第一个参数。

因此,让一个具有懒惰(和实时)评估时间的Event类只是写作的问题:

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)

并按照以下方式使用它:

>>> e= Event()
>>> e.time
' 17:25:8 '
>>> e.time
' 17:25:10 '

答案 2 :(得分:1)

不确定你所谈论的库的具体细节,但从算法的角度来看,我总是使用/补充它如下:(来自python新手的伪代码)

class Object:

    #... Other stuff ...

    _actual_property = None;

    def interface():
        if _actual_property is None:
            # Execute query and load up _actual_property

        return _actual_property

基本上因为接口和实现是分开的,您可以定义要根据请求执行的行为。

答案 3 :(得分:1)

机制非常简单:

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

然后,此实用程序可用作:

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

此实现使用callables来表示计算,但此主题有许多变体(例如,需要您使用evaluate()方法的基类等。)