Понимание генераторов в Python

ASP.NET MVC в основном позволяет Вам разделять ответственность различных разделов кода. Это позволяет Вам протестировать свое приложение. Можно протестировать Представления, Маршруты и т.д. Это также ускоряет приложение с тех пор теперь нет никакого ViewState или Обратной передачи.

, НО, существуют также недостатки. С тех пор Вы не использование WebForms, Вы не можете использовать управление ASP.NET. Это означает, хотите ли Вы создать GridView, Вы будете работать за циклом и составлять таблицу вручную. Если Вы хотите использовать Мастер ASP.NET в MVC тогда, необходимо будет создать самостоятельно.

Это - хорошая платформа, если Вы сыты по горло веб-формой ASP.NET и хотите выполнить все самостоятельно. Но необходимо ли иметь в виду это, Вы извлекли бы выгоду из создания всего материала снова или нет?

В целом я предпочитаю платформу Веб-форм из-за богатого комплекта средств управления и автоматической инфраструктуры.

194
задан Peter Mortensen 19 May 2018 в 22:44
поделиться

10 ответов

Примечание: этот пост предполагает синтаксис Python 3.x.

] Генератор - это просто функция, которая возвращает объект, для которого вы можете вызвать next , так что для каждого вызова он возвращает какое-то значение, пока не вызовет StopIteration ] исключение, сигнализирующее, что все значения были сгенерированы. Такой объект называется итератором .

Нормальные функции возвращают одно значение с использованием return , как и в Java. Однако в Python есть альтернатива, называемая yield . Использование yield в любом месте функции делает ее генератором. Обратите внимание на этот код:

>>> def myGen(n):
...     yield n
...     yield n + 1
... 
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Как видите, myGen (n) - это функция, которая возвращает n и n + 1 . Каждый вызов next возвращает одно значение, пока не будут получены все значения. for циклы вызывают next в фоновом режиме, таким образом:

>>> for n in myGen(6):
...     print(n)
... 
6
7

Подобным образом существуют выражения генератора , которые предоставляют средства для краткого описания некоторых распространенных типов генераторов:

>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Обратите внимание, что выражения генератора очень похожи на составления списков :

>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]

Обратите внимание, что объект-генератор генерируется один раз , но его код не запускается сразу. Только вызовы next фактически выполняют (часть) кода. Выполнение кода в генераторе останавливается, когда достигается оператор yield , после чего он возвращает значение. Следующий вызов next затем вызывает продолжение выполнения в состоянии, в котором генератор был оставлен после последнего yield . Это фундаментальное отличие от обычных функций: они всегда начинают выполнение «сверху» и сбрасывают свое состояние при возврате значения.

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

Теперь вы можете спросить: зачем использовать генераторы? На это есть несколько веских причин:

  • Некоторые концепции можно описать гораздо более кратко, используя генераторы.
  • Вместо создания функции, которая возвращает список значений, можно написать генератор, который генерирует значения на лету. Это означает, что не нужно создавать список, а это означает, что результирующий код более эффективен с точки зрения памяти. Таким образом, можно даже описывать потоки данных, которые были бы слишком велики для размещения в памяти.
  • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

     >>> def fib (): 

    Теперь вы можете спросить: зачем использовать генераторы? На это есть несколько веских причин:

    • Некоторые концепции можно описать гораздо более кратко, используя генераторы.
    • Вместо создания функции, которая возвращает список значений, можно написать генератор, который генерирует значения на лету. Это означает, что не нужно создавать список, а это означает, что результирующий код более эффективен с точки зрения памяти. Таким образом, можно даже описывать потоки данных, которые были бы слишком велики для размещения в памяти.
    • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

       >>> def fib (): 

      Теперь вы можете спросить: зачем использовать генераторы? На это есть несколько веских причин:

      • Некоторые концепции можно описать гораздо более кратко, используя генераторы.
      • Вместо создания функции, которая возвращает список значений, можно написать генератор, который генерирует значения на лету. Это означает, что не нужно создавать список, а это означает, что результирующий код более эффективен с точки зрения памяти. Таким образом, можно даже описывать потоки данных, которые были бы слишком велики для размещения в памяти.
      • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

         >>> def fib (): 
      • Вместо создания функции, которая возвращает список значений, можно написать генератор, который генерирует значения на лету. Это означает, что не нужно создавать список, а это означает, что результирующий код более эффективен с точки зрения памяти. Таким образом, можно даже описывать потоки данных, которые были бы слишком велики для размещения в памяти.
      • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

         >>> def fib (): 
      • Вместо создания функции, которая возвращает список значений, можно написать генератор, который генерирует значения на лету. Это означает, что не нужно создавать список, а это означает, что результирующий код более эффективен с точки зрения памяти. Таким образом, можно даже описывать потоки данных, которые были бы слишком велики для размещения в памяти.
      • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

         >>> def fib (): 
      • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

         >>> def fib (): 
      • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи :

         >>> def fib ():
        ... а, Ь = 0, 1
        ... пока True:
        ... дать
        ... а, Ь = Ь, а + Ь
        ... 
        >>> импортировать itertools
        >>> список (itertools.islice (fib (), 10))
        [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
        

        Этот код использует itertools.islice для извлечения конечного числа элементов из бесконечного потока. Рекомендуется внимательно изучить функции в модуле itertools , поскольку они являются важными инструментами для написания расширенных генераторов с большой легкостью.


      О Python <= 2.6: в приведенных выше примерах next - это функция, которая вызывает метод __ next __ для данного объекта. В Python <= 2.6 используется немного другая техника, а именно o.next () вместо next (o) . Python 2.7 имеет next () call .next , поэтому вам не нужно использовать следующее в 2.7:

      >>> g = (n for n in range(3, 5))
      >>> g.next()
      3
      
368
ответ дан 23 November 2019 в 05:23
поделиться

Прежде всего, термин генератор изначально был несколько некорректно определен в Python, что привело к большой путанице. Вы, вероятно, имеете в виду итераторы и итераторы (см. здесь ). Затем в Python есть также функции генератора (которые возвращают объект-генератор), объекты-генераторы (которые являются итераторами) и выражения генератора (которые оцениваются как генератор объекта).

Согласно глоссарий для генератора кажется, что официальная терминология теперь такова, что генератор является сокращением от «функции генератора». Раньше в документации термины определялись непоследовательно, но, к счастью, это было исправлено.

26
ответ дан 23 November 2019 в 05:23
поделиться

Генераторы можно рассматривать как сокращение для создания итератора. Они ведут себя как Java Iterator. Пример:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g)   # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next()  # iterator is at the end; calling next again will throw
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Надеюсь, это поможет / это то, что вы ищете.

Обновление:

Как показывают многие другие ответы, существуют разные способы создания генератора. Вы можете использовать синтаксис скобок, как в моем примере выше, или вы можете использовать yield. Еще одна интересная особенность состоит в том, что генераторы могут быть «бесконечными» - итераторы, которые не останавливаются:

>>> def infinite_gen():
...     n = 0
...     while True:
...         yield n
...         n = n + 1
... 
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...
22
ответ дан 23 November 2019 в 05:23
поделиться

Нет эквивалента в Java.

Вот немного надуманный пример:

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
        x = x + 1
        if x % 3 == 0:
            yield x

for a in mygen(100):
    print a

В генераторе есть цикл, который работает от 0 до n, и если переменная цикла кратно 3, он дает переменную.

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

11
ответ дан 23 November 2019 в 05:23
поделиться

Единственное, что я могу добавить к ответу Stephan202, - это рекомендация взглянуть на презентацию Дэвида Бизли PyCon '08 «Уловки с генератором для системных программистов», которая является лучшим объяснением как и почему из генераторов, которые я нигде видел. Это то, что привело меня от «Python выглядит забавно» к «Это то, что я искал». Это на http://www.dabeaz.com/generators/ .

6
ответ дан 23 November 2019 в 05:23
поделиться

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

>>> def myGenerator():
...     yield 'These'
...     yield 'words'
...     yield 'come'
...     yield 'one'
...     yield 'at'
...     yield 'a'
...     yield 'time'

>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

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

>>> for word in myGeneratorInstance:
...     print word
These
words
come
one
at 
a 
time

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

>>> from time import gmtime, strftime
>>> def myGen():
...     while True:
...         yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())    
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000   

Генератор инкапсулирует бесконечный цикл, но это не проблема, потому что вы получаете каждый ответ только каждый раз, когда просите об этом.

47
ответ дан 23 November 2019 в 05:23
поделиться

Я считаю, что первые итераторы и генераторы появились на языке программирования Icon, около 20 лет назад.

Вам может понравиться Обзор иконок , который позволяет вам обдумать их, не концентрируясь на синтаксисе (поскольку Icon - это язык, который вы, вероятно, не знаете, а Гризвольд объяснял преимущества его языка для людей, пришедших из других языков).

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

2
ответ дан 23 November 2019 в 05:23
поделиться

Это помогает провести четкое различие между функцией foo и генератором foo (n):

def foo(n):
    yield n
    yield n+1

foo - это функция. foo (6) - это объект-генератор.

Типичный способ использования объекта-генератора - это цикл:

for n in foo(6):
    print(n)

Цикл печатает

# 6
# 7

Думайте о генераторе как о возобновляемой функции.

yield ведет себя как return в том смысле, что полученные значения "возвращаются" генератором. Однако, в отличие от return, в следующий раз, когда генератор запрашивает значение, функция генератора, foo, возобновляет работу с того места, где была остановлена ​​- после последнего оператора yield - и продолжает работать, пока не встретит другой оператор yield.

За кулисами, когда вы вызываете bar = foo (6) , панель объекта генератора определяется для вас, чтобы иметь атрибут next .

Вы можете вызвать его самостоятельно, чтобы получить значения полученный из foo:

next(bar)    # Works in Python 2.6 or Python 3.x
bar.next()   # Works in Python 2.5+, but is deprecated. Use next() if possible.

Когда foo заканчивается (и больше нет возвращенных значений),

6
ответ дан 23 November 2019 в 05:23
поделиться

В этом сообщении числа Фибоначчи будут использоваться в качестве инструмента для объяснения полезности генераторов Python .

В этом сообщении будут представлены оба Код C ++ и Python.

Числа Фибоначчи определяются как последовательность: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....

Или в общем:

F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2

Это может быть чрезвычайно легко перенесено в функцию C ++:

size_t Fib(size_t n)
{
    //Fib(0) = 0
    if(n == 0)
        return 0;

    //Fib(1) = 1
    if(n == 1)
        return 1;

    //Fib(N) = Fib(N-2) + Fib(N-1)
    return Fib(n-2) + Fib(n-1);
}

Но если вы хотите вывести первые шесть чисел Фибоначчи, вы будете пересчитывать множество значений с помощью вышеуказанной функции.

Например: Fib (3) = Fib (2) + Fib (1) , но Fib (2) также пересчитывает Fib (1) . Чем выше значение, которое вы хотите вычислить, тем хуже вам будет.

Таким образом, у кого-то может возникнуть соблазн переписать приведенное выше, отслеживая состояние в main .

// Not supported for the first two elements of Fib
size_t GetNextFib(size_t &pp, size_t &p)
{
    int result = pp + p;
    pp = p;
    p = result;
    return result;
}

int main(int argc, char *argv[])
{
    size_t pp = 0;
    size_t p = 1;
    std::cout << "0 " << "1 ";
    for(size_t i = 0; i <= 4; ++i)
    {
        size_t fibI = GetNextFib(pp, p);
        std::cout << fibI << " ";
    }
    return 0;
}

Но это очень некрасиво и усложняет нашу логику в main . Было бы лучше не беспокоиться о состоянии в нашей функции main .

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

Итак, вернемся к нашему старому подходу, что произойдет, если мы захотим сделать что-то еще, кроме вывода чисел? Нам пришлось бы скопировать и вставить весь блок кода в main и изменить операторы вывода на все, что мы хотели сделать.

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

Итак, вернемся к нашему старому подходу, что произойдет, если мы захотим сделать что-то еще, кроме вывода чисел? Нам пришлось бы скопировать и вставить весь блок кода в main и изменить операторы вывода на все, что мы хотели сделать.

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

Итак, вернемся к нашему старому подходу, что произойдет, если мы захотим сделать что-то еще, кроме вывода чисел? Нам пришлось бы скопировать и вставить весь блок кода в main и изменить операторы вывода на все, что мы хотели бы сделать. А если скопировать и вставить код, то вас должны расстрелять. Вы же не хотите, чтобы вас застрелили?

Чтобы решить эти проблемы и избежать выстрела, мы можем переписать этот блок кода, используя функцию обратного вызова. Каждый раз, когда встречается новое число Фибоначчи, мы вызываем функцию обратного вызова.

void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t))
{
    if(max-- == 0) return;
    FoundNewFibCallback(0);
    if(max-- == 0) return;
    FoundNewFibCallback(1);

    size_t pp = 0;
    size_t p = 1;
    for(;;)
    {
        if(max-- == 0) return;
        int result = pp + p;
        pp = p;
        p = result;
        FoundNewFibCallback(result);
    }
}

void foundNewFib(size_t fibI)
{
    std::cout << fibI << " ";
}

int main(int argc, char *argv[])
{
    GetFibNumbers(6, foundNewFib);
    return 0;
}

Это явно улучшение, ваша логика в main не так загромождена, и вы можете делать все, что захотите, с помощью Фибоначчи. числа, просто определите новые обратные вызовы.

Но это все еще не идеально. Что, если бы вы хотели получить только первые два числа Фибоначчи, а затем сделать что-то, затем получить еще немного, а затем сделать что-то еще?

Что ж, мы могли бы продолжить, как и раньше, и мы могли бы снова начать добавлять состояние в main , позволяющий GetFibNumbers начинать с произвольной точки. Но это еще больше раздувает наш код, и он уже кажется слишком большим для такой простой задачи, как печать чисел Фибоначчи.

Мы могли бы реализовать модель производителя и потребителя с помощью пары потоков. Но это еще больше усложняет код.

Вместо этого давайте поговорим о генераторах.

У Python есть очень хорошая языковая функция, которая решает подобные проблемы, называемые генераторами.

Генератор позволяет вам выполнять функцию, останавливаясь на произвольную точку, а затем продолжить с того места, где вы остановились. Каждый раз возвращает значение.

Рассмотрим следующий код, который использует генератор:

def fib():
    pp, p = 0, 1
    while 1:
        yield pp
        pp, p = p, pp+p

g = fib()
for i in range(6):
    g.next()

Что дает нам результаты:

0 1 1 2 3 5

Оператор yield используется вместе с генераторами Python. Он сохраняет состояние функции и возвращает полученное значение. В следующий раз, когда вы вызовете функцию next () в генераторе, она продолжит работу с того места, где была остановлена ​​yield.

Это намного более чисто, чем код функции обратного вызова. У нас более чистый код, меньший код и, не говоря уже о гораздо более функциональном коде (Python допускает сколь угодно большие целые числа).

Источник

5
ответ дан 23 November 2019 в 05:23
поделиться

Мне нравится описывать генераторы тем, у кого есть приличный опыт в языках программирования и вычислениях, в терминах фреймов стека.

Во многих языках существует стек, поверх которого находится текущий «фрейм» стека. Кадр стека включает пространство, выделенное для переменных, локальных для функции, включая аргументы, переданные этой функции.

Когда вы вызываете функцию, текущая точка выполнения («счетчик программы» или эквивалент) помещается в стек, и создается новый кадр стека. Затем выполнение переходит к началу вызываемой функции.

С обычными функциями в какой-то момент функция возвращает значение, и стек "выталкивается". Кадр стека функции отбрасывается, и выполнение возобновляется в предыдущем месте.

Когда функция является генератором, она может возвращать значение без отбрасывания кадра стека, используя оператор yield. Значения локальных переменных и программного счетчика внутри функции сохраняются. Это позволяет возобновить работу генератора в более позднее время с продолжением выполнения из оператора yield, и он может выполнять больше кода и возвращать другое значение.

До Python 2.5 это были все генераторы. Python 2.5 также добавил возможность передавать значения обратно в в генератор. При этом переданное значение доступно как выражение, являющееся результатом оператора yield, который временно возвратил управление (и значение) из генератора.

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

8
ответ дан 23 November 2019 в 05:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: