Восстановить определяющее выражение для генератора питона - PullRequest
2 голосов
/ 14 января 2012

дан генератор

g = ( <expr> for x in <iter> ),

есть ли способ восстановить выражение и итератор, использованный для определения g?

Например, функция, которая будет вести себя так:

expr, iter = f( ( x*x for x in range(10) ) )
expr(2) # 4
expr(5) # 25
iter[1] # 1
iter[9] # 9
iter[10] # raises IndexError

Причина, по которой я хочу эту функциональность, заключается в том, что я создал свой собственный класс LazyList. Я хочу, чтобы он по существу вел себя как генератор, за исключением разрешения доступа через getitem без необходимости перебирать элементы k-1, прежде чем он сможет получить доступ к k-му элементу. Спасибо.

Редактировать: Вот снимок класса ленивого списка:

class LazyList(object):
  def __init__(self, iter=None, expr=None):
    if expr is None:
      expr = lambda i: i
    if iter is None:
      iter = []
    self._expr = expr
    self._iter = iter

  def __getitem__(self, key):
    if hasattr(self._iter, '__getitem__'):
      return self._expr(self._iter[key])
    else:
      return self._iter_getitem(key)

  def __iter__(self):
    for i in self._iter:
      yield self._expr(i)

Я опустил метод _iter_getitem. Все, что он делает, это перебирает _iter, пока не достигнет ключевого элемента (или не использует iserice itertool, если key является слайсом). Есть также общие функции llmap, llreduce и т. Д., Которые я пропустил, но вы, вероятно, можете догадаться, как они идут.

Одна из причин, по которой я хочу разложить генераторы, заключается в том, что я могу элегантно инициализировать этот класс, например

l = LazyList(x*x for x in range(10))

вместо

l = LazyList(range(10), lambda x: x*x)

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

Я часто использую это с Django, потому что он хорошо работает с их наборами запросов. У меня есть много кода, который зависит от ленивых структур списков, потому что он возвращает многомерные массивы, которые, если их оценить, извлекут намного больше данных, чем мне нужно.

Ответы [ 2 ]

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

Самое близкое, что я могу придумать, - это разобрать объект кода, который находится внутри выражения генератора.Что-то вроде

>>> import dis
>>> g = ( x*x for x in range(10) )
>>> dis.dis(g.gi_code)
  1           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                15 (to 21)
              6 STORE_FAST               1 (x)
              9 LOAD_FAST                1 (x)
             12 LOAD_FAST                1 (x)
             15 BINARY_MULTIPLY     
             16 YIELD_VALUE         
             17 POP_TOP             
             18 JUMP_ABSOLUTE            3
        >>   21 LOAD_CONST               0 (None)
             24 RETURN_VALUE        

Это дает небольшую подсказку о том, что происходит, но это не очень понятно, ИМХО.

Есть еще один вопрос переполнения стека , который имеет дело спреобразование байтового кода Python в читаемый Python - возможно, вы можете использовать это, чтобы получить что-то более читаемое человеком.

0 голосов
/ 14 января 2012

Я думаю, что ваша концепция LazyList хороша, но ваши мысли о прямом доступе к n-му значению генератора ошибочны.Ваш пример использования range(10) в качестве последовательности для итерации - это особый случай, в котором все значения известны заранее.Но многие генераторы вычисляются постепенно, в которых n-е значение вычисляется на основе n-1-го значения.Генератор Фибоначчи является одним из таких:

def fibonacci(n=1000):
    a,b=1,1
    yield a
    while n>0:
        n -= 1
        yield b
        a,b = b,a+b

Это дает знакомую серию 1, 1, 2, 3, 5, 8, ..., в которой n-й элемент является суммой n-1-й и 2-й.Таким образом, невозможно перейти непосредственно к пункту 10, вам нужно пройти через пункты 0-9.

При этом ваш LazyList хорош по нескольким причинам:

  • позволяет вернуться к более ранним значениям

  • имитирует прямой доступ, даже если под прикрытием генератор должен пройти все инкрементные значения, пока не достигнет значения 'n'

  • он только вычисляет фактически требуемые значения, поскольку генератор вычисляется лениво, вместо того, чтобы предварительно вычислять только 1000 значений, чтобы найти, что используются первые 10

...