Как выполняется отложенная оценка (например, в ORM) - PullRequest
2 голосов
/ 23 января 2012

Мне любопытно узнать, как ленивая оценка реализована на более высоких уровнях, то есть в библиотеках и т. Д. Например, как Django ORM или ActiveRecord откладывает оценку запроса до его фактического использования?

Ответы [ 4 ]

7 голосов
/ 23 января 2012

Давайте посмотрим на некоторые методы класса django.db.models.query.QuerySet django:

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, и любой последующий вызов того же метода возвращает кэшированное значение.

5 голосов
/ 23 января 2012

В 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 '
1 голос
/ 23 января 2012

Механизм довольно прост:

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

В этой реализации для представления вычислений используются вызываемые элементы, но существует много вариантовна эту тему (например, базовый класс, который требует от вас реализации метода evaluate() и т. д.).

1 голос
/ 23 января 2012

Не уверен насчет специфики, о какой библиотеке вы говорите, но с точки зрения алгоритма я всегда использовал / недооценивал ее следующим образом: (код psuedo от новичка в 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

По существупоскольку интерфейс и реализация разделены, вы можете определить поведение, которое будет выполняться по запросу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...