Выход только столько, сколько требуется от генератора

1

Я хочу получить из генератора только столько элементов, сколько требуется.

В следующем коде

a, b, c = itertools.count()

Я получаю это исключение:

ValueError: too many values to unpack

Я видел несколько связанных вопросов, однако у меня нет нулевого интереса к остальным элементам из генератора, я только хочу получить столько, сколько я прошу, , не указав это количество заранее.

Мне кажется, что Python определяет количество элементов, которые вы хотите, но затем пытается попытаться прочитать и сохранить больше, чем это число (создание ValueError).

Как я могу предоставить только столько элементов, сколько мне нужно, не передавая сколько элементов я хочу?

Update0

Чтобы помочь понять приблизительное поведение, я считаю, что это возможно, здесь пример кода:

def unpack_seq(ctx, items, seq):
    for name in items:
        setattr(ctx, name, seq.next())

import itertools, sys
unpack_seq(sys.modules[__name__], ["a", "b", "c"], itertools.count())
print a, b, c

Если вы можете улучшить этот код, пожалуйста, сделайте.

Ответ Alex Martelli предлагает мне, что байт op UNPACK_SEQUENCE отвечает за ограничение. Я не понимаю, почему эта операция должна требовать, чтобы сгенерированные последовательности также должны точно соответствовать длине.

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

Теги:
generator
python-2.x
yield

4 ответа

6
Лучший ответ

Вам нужен глубокий байт-код hack - то, что вы запрашиваете, не может быть выполнено на уровне исходного кода Python, но (если вы согласны совершить конкретную версию и выпуск Python), может быть выполнимо, после обработки байт-кода после того, как Python скомпилировал его. Рассмотрим, например:

>>> def f(someit):
...   a, b, c = someit()
... 
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (someit)
              3 CALL_FUNCTION            0
              6 UNPACK_SEQUENCE          3
              9 STORE_FAST               1 (a)
             12 STORE_FAST               2 (b)
             15 STORE_FAST               3 (c)
             18 LOAD_CONST               0 (None)
             21 RETURN_VALUE        
>>> 

Как вы видите, байт-код UNPACK_SEQUENCE 3 происходит без того, чтобы итератор someit получал наименьшее указание на него (итератор уже был вызван!) - так что вы должны префикс его в байт-коде с помощью "получить точно N байтов", например:

>>> def g(someit):
...   a, b, c = limit(someit(), 3)
... 
>>> dis.dis(g)
  2           0 LOAD_GLOBAL              0 (limit)
              3 LOAD_FAST                0 (someit)
              6 CALL_FUNCTION            0
              9 LOAD_CONST               1 (3)
             12 CALL_FUNCTION            2
             15 UNPACK_SEQUENCE          3
             18 STORE_FAST               1 (a)
             21 STORE_FAST               2 (b)
             24 STORE_FAST               3 (c)
             27 LOAD_CONST               0 (None)
             30 RETURN_VALUE        

где limit - ваша собственная функция, легко реализуемая (например, через itertools.slice). Таким образом, исходная 2-байт-кодовая последовательность "load fast; call function" (как раз перед байт-кодом для распаковки) должна стать такой 5-байт-кодовой последовательностью с глобальным байтовым кодом нагрузки для limit перед исходной последовательностью и последовательность load-const; call function после него.

Конечно, вы можете реализовать это байт-код в декораторе.

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

Это стоит того, чтобы использовать его? Абсолютно нет, конечно, - смешной объем работы за крошечный "синтаксический сахар". Тем не менее, это может быть поучительный проект, чтобы преуспеть в овладении хакерами в бат-кодах, взлом атак и другие трюки с черной магией, которые вам, вероятно, никогда не понадобятся, но, безусловно, здорово знать, когда вы хотите выйти за пределы языка простого мастера языка что у гуру мирового класса - действительно, я подозреваю, что те, кто мотивирован стать гуру, - это, как правило, люди, которые не могут не упустить очарование таких "механических механизмов языка"... и тех, кто на самом деле сделайте, чтобы этот высокий уровень был подмножеством достаточно мудрым, чтобы понять, что такие попытки "просто играют" и преследуют их как деятельность свободного времени (умственный эквивалент поднятия тяжестей, например, судоку или кроссворды; -), не позволяя им вмешиваться в важные задачи (обеспечивая ценность для пользователей, развертывая прочный, понятный, простой, хорошо исполняемый, хорошо проверенный, хорошо документированный код, чаще всего без даже малейший намек черной магии к ней; -).

  • 1
    Всегда приятно читать ваши ответы.
  • 0
    @ Феликс, спасибо!
Показать ещё 2 комментария
6

Вам нужно убедиться, что количество элементов на обеих сторонах соответствует. Один из способов - использовать islice из модуля itertools:

from itertools import islice
a, b, c = islice(itertools.count(), 3)
3

Python не работает так, как вам хочется. В любом присваивании, например

a, b, c = itertools.count()

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

  • 1
    я не понимаю, почему он не может перебирать переменные LHS, беря из RHS, пока не осталось переменных для заполнения, и делая эквивалент "перерыва"
1

Или используйте понимание списка, так как вы знаете нужное количество элементов:

ic = itertools.count()
a,b,c = [ic.next() for i in range(3)]

или даже проще:

a,b,c = range(3)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню