- Почему генераторы 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, innext(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 выпустил значение.
Доходность была принципиально изменилась с добавлением отправки.
- Переопределение доходности быть выражением, а не заявлением. Текущее заявление дохода станет выражением доходности, значение которого выброшено. Значение выражения доходности является не всякий раз, когда генератор возобновляется нормальным следующим () вызовом.
- Добавьте новый метод отправки () для генераторных итераторов, который возобновляет генератор и отправляет значение, которое становится результатом текущего выражения доходности. Метод отправки () возвращает следующее значение, полученное генератором, или повышает запечатление, если генератор выходит без приема другого значения.
Отправить (нет) также можно использовать вместо первого следующего ()
Получение отправки
Как мы получим отправить? Хитрый вопрос действительно. Вот мини-фрагмент, показывающий, как [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 частей:
- Генераторные трюки для системных программистов
- Любопытный курс на Coroutines и параллелизма
- Генераторы: финальная граница
Это также включает много случаев использования.
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”