Рубрики
Без рубрики

Генераторы Python: Углубленная статья, которую вы всегда хотели

Содержание, почему введены генераторы Python? Как разыжаются генераторы Python … Помечено Python, генераторы, асинсио.

  • Почему генераторы Python представили?
  • Как генераторы Python отличаются от нормальных функций?
  • Поток выполнения
  • Немедленная полезность
  • Далее и для петлей
  • Генераторы, введенные для экономии памяти
  • Генераторы для задач
  • Метод отправки
  • Получение отправки
  • Что уступает от
  • Последняя часть
  • Предел генераторов: бесконечность и за его пределами

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

Почему генераторы Python представили?

Прежде чем представлять генераторы и синтаксис, важно знать, почему в первую очередь были введены генераторы. Ключевое слово доходности не означает генераторы. Нужно понять концепцию позади. Оригинальный PEP представил «Концепция генераторов в Python, а также новое заявление, используемое в сочетании с ними, оператор выхода» [1].

Общее использование случая генераторов выглядит следующим образом:

Когда функция продюсера имеет достаточно жесткую работу, что требует обслуживания состояния между выпущенными значениями, [1]

И более явнее

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

Таким образом, мы понимаем, что были необходимы новые виды функций, что:

  • 1. Возвращать промежуточные значения
  • 2. Сохранить состояние функций

Как генераторы Python отличаются от нормальных функций?

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

Нормальная функция:

def x():
    print('abc')
    return 
    print('def') # not reached
x()

В приведенном выше примере def не будет напечатан, так как функция вышла до. Давайте рассмотрим основной пример генератора:

def x():
  a = 0
  while 1:
    yield a

    a += 1

z = x()

print(z)
print(next(z))
print(next(z))

0
1

Из приведенного выше примера, как только мы позвонили следующим, он вернул значение. Целью следующего является перейти к следующему утверждению . Когда мы позвонили сейчас в первый раз, он пошел на следующую доходность с самого начала, когда А был изначально в 0.

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

По А Обновление мы видим, что даже когда функция была вышла в первый раз, когда программа вернулась к ней, она продолжалась в предыдущем состоянии, когда А было 0. Это достигает двух целей возможность возобновить функции и сохранение предыдущего состояния.

Чтобы понять это лучше, вот еще несколько названий, которые были предложены вместо урожая [1], но были в конечном итоге отклонены:

  • Вернуться 3 и продолжить
  • Возврат и продолжить 3
  • Возврат генерации 3
  • Продолжить возврат 3

Гвидо дает сводку генераторов [1]:

На практике (как вы думаете о них), генераторы являются функциями, но с поворотом, которые они возобновляются.

Поток выполнения

Следующий фрагмент дает нам представление о выполнении:

def x():
  print('started')
  while 1:
    print('before yield')
    yield
    print('after yield')


z = x()

next(z)
print('-- 2nd call')
next(z)
started
before yield
-- 2nd call
after yield
before yield

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

Доходность не должно быть в бесконечных петель, вы можете использовать несколько одновременно в одном и том же корпусе функций:

def x():
  print('start')
  yield 
  print('after 1st yield')
  yield
  print('after 2nd yield')

z = x()
next(z)
next(z)
next(z)
start
after 1st yield
after 2nd yield
Traceback (most recent call last):
  File "lab.py", line 11, in 
    next(z)
StopIteration

В случае, если вы позвонили в следующем больше, чем выявления доходности, генераторные функции поднимают Заставка Отказ Если вы хотите автоматически обрабатывать Заставка Пока не осталось большего, используйте … A для Loop:

def x():
  print('start')
  yield 
  print('after 1st yield')
  yield
  print('after 2nd yield')

z = x()
for _ in z:
  pass

Если вы вернете значение, переменная петли будет равна этого значения:

def x():
  print('start')
  yield 1
  print('after 1st yield')
  yield 2
  print('after 2nd yield')

z = x()
for _ in z:
  print(_)
start
1
after 1st yield
2
after 2nd yield

Немедленная полезность

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

def odd_till(number):
  n = 1
  while n < number:
    yield n
    n += 2

for odd_num in odd_till(10):
  print(odd_num)

Получаем один номер, и выходы функции, циклический цикл снова вызывает. Это дает одно число и выходы. И так далее. Это происходит об этом в микроэтам. Операции, завершенные в одном цикле, это просто приращение n и чек n <номер Отказ

strad_till (10) или strad_till (100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) Не вызывайте ошибки памяти

Далее и для петлей

Две вещи могут пазлировать вас:

  • Почему бы следующий использовал?
  • Как функция с 2 допустимой работой, когда с ним используется A для цикла?

Ответ заключается в том, что генераторы внедряют протоколы итератора, то же самое использование списками. Вот класс, настроенный для выступления генератора [7]:

# Using the generator pattern (an iterable)
class firstn(object):
    def __init__(self, n):
        self.n = n
        self.num = 0

    def __iter__(self):
        return self

    # Python 3 compatibility
    def __next__(self):
        return self.next()

    def next(self):
        if self.num < self.n:
            cur, self.num = self.num, self.num+1
            return cur
        else:
            raise StopIteration()

sum_of_first_n = sum(firstn(1000000))

Генераторы, введенные для экономии памяти

Рассмотрим список пометков:

sum([x*x for x in range(10)])

Выражение генератора много, гораздо более эффективно [2]:

sum(x*x for x in range(10))

Это было второе добавление в истории генератора.

Генераторы для задач

Давайте изменим наши две функции с помощью печати

def odd_till(number):
  n = 1
  while n < number:
    print('odd_till {} currently: {}'.format(number, n))
    yield n
    n += 2

def even_till(number):
  n = 0
  while n < number:
    print('even_till {} currently: {}'.format(number, n))
    yield n
    n += 2

Позволяет иметь класс для запуска функций

from collections import deque

class RunFunc:
  def __init__(self):
    self._queue = deque()

  def add_func(self, func):
    self._queue.append(func)

  def run(self):
    while self._queue:
      func = self._queue.popleft()
      try:
        next(func)
        self._queue.append(func)
      except StopIteration:
        pass

использование

func_runner = RunFunc()

func_runner.add_func(odd_till(5))
func_runner.add_func(even_till(4))
func_runner.add_func(odd_till(6))
func_runner.run()

вывод

odd_till 5 currently: 1
even_till 4 currently: 0
odd_till 6 currently: 1
odd_till 5 currently: 3
even_till 4 currently: 2
odd_till 6 currently: 3
odd_till 6 currently: 5

Если мы переименовываем то же самое, что мы получаем мини-планировщик задач [4]

from collections import deque

class TaskScheduler:
  def __init__(self):
    self._queue = deque()

  def add_task(self, task):
    self._queue.append(task)

  def run(self):
    while self._queue:
      task = self._queue.popleft()
      try:
        next(task)
        self._queue.append(task)
      except StopIteration:
        pass

использование

scheduler = TaskScheduler()

scheduler.add_task(odd_till(5))
scheduler.add_task(even_till(4))
scheduler.add_task(odd_till(6))
scheduler.run()

Просто точка заметки, почему мы удаляем задачу (Popleft) и readd it (Добавить)?

      task = self._queue.popleft()
      try:
        next(task)
        self._queue.append(task)
      except StopIteration:
        pass

Это потому, что если это было закончено (исключение поднято) Ну и хорошо, он пойдет прямо к блоке. Иначе .append будет выполняться.

Другими словами, если задача завершается, не добавьте ее обратно, добавьте его обратно.

Метод отправки

Генераторы поддерживают способ отправки значений генераторам

def times2():
    while True:
        val = yield
        yield val * 2

z = times2()


next(z)
print(z.send(1))
next(z)
print(z.send(2))
next(z)
print(z.send(3))
2
4
6

Это было важное дополнение. Этот отрывок объясняет, почему отправить введен и почему он важен в Asyncio [6]:

Функции генератора Python почти Conoutines – но не совсем – в том, что они позволяют приостановить выполнение для создания значения, но не предусматривайте значения или исключения, которые будут передаваться, когда выполнение возобновляется … Однако, если бы можно было пропустить значения или исключения в генератор в точке, где он был приостановлен, простой совместительный планировщик или функция батупла отпускают, позволили бы позвонить в CONOUTINES позвонить друг другу без блокировки – огромный бон для асинхронных приложений. Такие приложения могут затем написать ко-процедуры для неблокирующего разъема ввода/вывода, обеспечивая контроль на планировщик ввода/вывода, пока данные не будут отправлены или не станут доступен. Между тем, код, который выполняет ввод/вывод, просто сделает что-то подобное: данные = (выход неблокировки_read (my_socket, nbytes)) Для того, чтобы приостановить выполнение до nonblocking_read () COROUTINE выпустил значение.

Доходность была принципиально изменилась с добавлением отправки.

    1. Переопределение доходности быть выражением, а не заявлением. Текущее заявление дохода станет выражением доходности, значение которого выброшено. Значение выражения доходности является не всякий раз, когда генератор возобновляется нормальным следующим () вызовом.
    1. Добавьте новый метод отправки () для генераторных итераторов, который возобновляет генератор и отправляет значение, которое становится результатом текущего выражения доходности. Метод отправки () возвращает следующее значение, полученное генератором, или повышает запечатление, если генератор выходит без приема другого значения.

Отправить (нет) также можно использовать вместо первого следующего ()

Получение отправки

Как мы получим отправить? Хитрый вопрос действительно. Вот мини-фрагмент, показывающий, как [4]

from collections import deque

class ActorScheduler:
  def __init__(self):
    self._actors = { } # Mapping of names to actors
    self._msg_queue = deque() # Message queue

  def new_actor(self, name, actor):
    '''
    Admit a newly started actor to the scheduler and give it a name
    '''
    self._msg_queue.append((actor,None))
    self._actors[name] = actor

  def send(self, name, msg):
    '''
    Send a message to a named actor
    '''
    actor = self._actors.get(name)
    if actor:
      self._msg_queue.append((actor,msg))

  def run(self):
    '''
    Run as long as there are pending messages.
    '''
    while self._msg_queue:
      actor, msg = self._msg_queue.popleft()
      try:
        actor.send(msg)
      except StopIteration:
        pass

# Example use
if __name__ == '__main__':
  def printer():
    while True:
      msg = yield
      print('Got:', msg)

  def counter(sched):
    while True:
    # Receive the current count
      n = yield
      if n == 0:
        break
      # Send to the printer task
      sched.send('printer', n)
      # Send the next count to the counter task (recursive)
      sched.send('counter', n-1)


  sched = ActorScheduler()
  # Create the initial actors
  sched.new_actor('printer', printer())
  sched.new_actor('counter', counter(sched))
  # Send an initial message to the counter to initiate
  sched.send('counter', 100)
  sched.run()

Вышесказанное можно расширить с большим количеством областей, таких как готовые, готовые к чтению, готовые для записи и записи соответствующего кода для переключения между областями и … У вас есть одновременное приложение. Это основы операционной системы [4]. Использование ench.send Позволяет иметь петлю за пределами лимита рекурсии Python. Предел рекурсии – Импорт SYS; sys.getrecursionlimit () Обычно 1000. попробуйте ench.send ('Counter', 1001) Отказ

Из чего уступает?

Рассмотрим следующий код:

def gen_alph():
  for a in 'abc':
    yield a

def gen_nums():
  for n in '123':
    yield n


def gen_data():
  yield from gen_alph()
  yield from gen_nums()


for _ in gen_data():
  print(_)
a
b
c
1
2
3

Он ведет себя точно так, как будто алфавит и числовые петли со своими соответствующими выходами были внутри Gen_data.

«Уход от генераторов в качестве вызовов заключается в функциях», поскольку Brett Cannon ставит его [8]

Последняя часть

Генераторы имеют тесный метод, пойманный с помощью Generatorexit исключение:

def gen_alph():
  try:
    for a in 'abc':
      yield a
  except GeneratorExit:
      print('Generator exited')

z = gen_alph()
next(z)
z.close()
Generator exited

У них также есть метод броска, чтобы поймать ошибки:

def gen_alph():
  try:
    for a in 'abc':
      yield a
  except GeneratorExit:
      print('Generator exited')
  except Exception:
    yield 'error occured'

z = gen_alph()
next(z)
print(z.throw(Exception))
error occured

Предел генераторов: бесконечность и за его пределами

Если вы действительно хотите, чтобы лучшие генераторы Python в Интернете могут высказать вас скопированным и снова на сайтах Python, см. Серия David Beazley’s 3 частей:

Это также включает много случаев использования.

Abdur-Rahmaan Janhangeer – это организационный член Python Mauritius пользователь группы (Гамыуг), Фласкукон и поддерживает pythonkitchen.com Отказ Настоящая статья – продолжение его разговора о вытекании Asyncio

использованная литература

[1] https://www.python.org/dev/peps/pep-0255/ [2] https://www.python.org/dev/peps/pep-0289/ [3] https://dev.to/abdurrahmaanj/add-superpowers-to-your-python-lists-us–tis-feature–4nf. [4] Python Cookbook, Дэвид Безли [5] https://docs.cython.org/3/Library/asyncio-task.html [6] https://www.python.org/dev/peps/pep-0342/ [7] https://wiki.python.org/moin/generators [8] BRETT CANOON: Python 3.3: Поверь мне, это лучше, чем Python 2.7

Оригинал: “https://dev.to/abdurrahmaanj/python-generators-the-in-depth-article-you-ve-always-wanted-96p”