懒惰求值是如何实现的(例如在ORM中)

2 投票
4 回答
1417 浏览
提问于 2025-04-17 10:54

我很好奇懒惰求值是如何在更高层次上实现的,比如在一些库里面。举个例子,像Django的ORM或者ActiveRecord是怎么把查询的执行推迟到真正需要的时候才去做的呢?

4 个回答

1

我不太确定你说的是哪个库,但从算法的角度来看,我一直是这样理解的:(这是一个初学者用的伪代码)

class Object:

    #... Other stuff ...

    _actual_property = None;

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

        return _actual_property

简单来说,因为接口和实现是分开的,所以你可以定义在请求时要执行的行为。

6

在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”关键字,这让事情变得更简单——你只需将生成属性的代码方法作为第一个参数传递给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 '
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 里,之后再调用同样的方法时,就会直接返回这个缓存的值。

撰写回答