Генератор в качестве сопрограммы (без резьбы)
Давайте получим FakeFtp
с функцией retrbinary
с использованием обратного вызова, вызываемого при каждом успешном чтении фрагмента данных:
class FakeFtp(object):
def __init__(self):
self.data = iter(["aaa", "bbb", "ccc", "ddd"])
def login(self, user, password):
self.user = user
self.password = password
def retrbinary(self, cmd, cb):
for chunk in self.data:
cb(chunk)
Недостатком использования простой функции обратного вызова является то, что она вызывается повторно, а функция обратного вызова
функция не может легко сохранять контекст между вызовами.
Следующий код определяет process_chunks
генератор, который сможет получать порции данных один
по одному и обрабатывая их. В отличие от простого обратного вызова, здесь мы можем сохранить все
обработка внутри одной функции без потери контекста.
from contextlib import closing
from itertools import count
def main():
processed = []
def process_chunks():
for i in count():
try:
# (repeatedly) get the chunk to process
chunk = yield
except GeneratorExit:
# finish_up
print("Finishing up.")
return
else:
# Here process the chunk as you like
print("inside coroutine, processing chunk:", i, chunk)
product = "processed({i}): {chunk}".format(i=i, chunk=chunk)
processed.append(product)
with closing(process_chunks()) as coroutine:
# Get the coroutine to the first yield
coroutine.next()
ftp = FakeFtp()
# next line repeatedly calls `coroutine.send(data)`
ftp.retrbinary("RETR binary", cb=coroutine.send)
# each callback "jumps" to `yield` line in `process_chunks`
print("processed result", processed)
print("DONE")
Чтобы увидеть код в действии, поместите класс FakeFtp
, код, показанный выше, и следующую строку:
main()
в один файл и назовите его:
$ python headsandtails.py
('inside coroutine, processing chunk:', 0, 'aaa')
('inside coroutine, processing chunk:', 1, 'bbb')
('inside coroutine, processing chunk:', 2, 'ccc')
('inside coroutine, processing chunk:', 3, 'ddd')
Finishing up.
('processed result', ['processed(0): aaa', 'processed(1): bbb', 'processed(2): ccc', 'processed(3): ddd'])
DONE
Как это работает
processed = []
здесь, чтобы показать, генератор process_chunks
не должен иметь никаких проблем с
сотрудничать с его внешним контекстом. Все завернуто в def main():
, чтобы доказать, нет необходимости
используйте глобальные переменные.
def process_chunks()
является ядром решения. Может иметь один входной параметр (не
используется здесь), но основной точкой, где он получает ввод, является каждая строка yield
, возвращающая то, что кто-то отправляет
через .send(data)
в экземпляр этого генератора. Можно coroutine.send(chunk)
, но в этом примере это делается с помощью обратного вызова, ссылающегося на эту функцию callback.send
.
Обратите внимание, что в реальном решении нет проблем иметь несколько yield
s в коде, они
обрабатывается по одному. Это может быть использовано, например, читать (и игнорировать) заголовок файла CSV, а затем
продолжить обработку записей с данными.
Мы могли бы создать экземпляр и использовать генератор следующим образом:
coroutine = process_chunks()
# Get the coroutine to the first yield
coroutine.next()
ftp = FakeFtp()
# next line repeatedly calls `coroutine.send(data)`
ftp.retrbinary("RETR binary", cb=coroutine.send)
# each callback "jumps" to `yield` line in `process_chunks`
# close the coroutine (will throw the `GeneratorExit` exception into the
# `process_chunks` coroutine).
coroutine.close()
Реальный код использует contextlib
closing
менеджер контекста, чтобы убедиться, что coroutine.close()
всегда звонил.
Выводы
Это решение не предоставляет своего рода итератор для использования данных в традиционном стиле "из
снаружи ". С другой стороны, мы можем:
- использовать генератор "изнутри"
- хранить всю итеративную обработку внутри одной функции без прерывания между обратными вызовами
- опционально использовать внешний контекст
- предоставляет полезные результаты за пределами
- все это можно сделать без использования потоков
Кредиты : Решение в значительной степени вдохновлено ответом SO Итератор Python FTP «chunk» (без загрузки всего файла в память)
Автор: user2357112