Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Сопрограммы в Python

Anna | 16.06.2014 | нет комментариев
Предлагаю обсудить такую увлекательную, но немного используемую вероятность python, как сопрограммы (coroutines).
Сопрограммы в питоне основаны на генераторах (ими, они, собственно и являются).
Следственно, предлагаю начать именно с генераторов, в всеобщем понимании. А потом разберём как написать свою сопрограмму.

Генераторы

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

Я подразумеваю что вы знаете как они работают, следственно легко напомню, как это выглядит.
Возьмём вот такую функцию:

def read_file_line_by_line(file_name):
  with open(file_name, 'r') as f:
      while True:
        line = f.readline()
        if not line:
          break
        yield line

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

В всеобщем случае, работа с генераторами выглядит дальнейшим образом:

In [78]: lines_generator = read_file_line_by_line("data.csv")
In [79]: type(lines_generator)
Out[79]: generator
In [83]: lines_generator.next()
Out[83]: 'time,host,eventn'
In [84]: lines_generator.next()
Out[84]: '1374039728,localhost,rebootn'
In [85]: lines_generator.next()
Out[85]: '1374039730,localhost,startn'
In [86]: lines_generator.next()

---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-86-65df1a2cb71b> in <module>()

----> 1 lines_generator.next()

StopIteration: 

# Соответственно у меня в файле только 3 строчки 
# Как только читать огромнее нечего, появляется исключение StopIteration, как и с любым итерируемым оъектом. 

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

uniq = []
for line in lines_generator:
  if line not in uniq:
      uniq.append(line)

Так же допустима короткая запись генератора:

In [92]: gen = (x for x in xrange(0, 100*10000))
In [93]: gen.next()
Out[93]: 0
In [94]: gen.next()
Out[94]: 1
In [95]: gen.next()
Out[95]: 2
In [96]: gen.next()
Out[96]: 3
In [97]: gen.next()
Out[97]: 4

Схоже на списковые выражения, правильно? Только не требует создания каждого списка range(0, 100*10000) в памяти, возвращаемое значение «вычисляется» всякий раз при обращении.

Сопрограммы как частный случай генераторов

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

О эталоне дозволено почитать здесь PEP 342.

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

def calc():
    history = []
    while True:
        x, y = (yield)
        if x == 'h':
            print history
            continue
        result = x   y
        print result
        history.append(result)

c = calc()

print type(c) # <type 'generator'>

c.next() # Нужная инициация. Дозволено написать c.send(None)
c.send((1,2)) # Выведет 3
c.send((100, 30)) # Выведет 130
c.send((666, 0)) # Выведет 666
c.send(('h',0)) # Выведет [3, 130, 666]
c.close() # Закрывем генератор

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

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

def coroutine(f):
    def wrap(*args,**kwargs):
        gen = f(*args,**kwargs)
        gen.send(None)
        return gen
    return wrap

@coroutine
def calc():
    history = []
    while True:
        x, y = (yield)
        if x == 'h':
            print history
            continue
        result = x   y
        print result
        history.append(result)

На этом примере дозволено осознать как писать свои больше трудные (и пригодные) сопрограммы.

Завершение.

Хоть задачи, которые дозволено решить этим инструментом затрагивают дюже многие области (такие как асинхронное программирование), многие разработчики выбирают больше привычные инструменты ООП. Но при этом сопрограммы могут быть дюже пригодным инструментом в вашем арсенале, от того что они довольно наглядны, а создание фунций больше дешёвая операция по сопоставлению с созданием объекта класса.

Да и определённый академический интерес они представляют, как мне кажется.

Вот такая вот первая статья.

UPD:
Поправил в примере короткой записи генератора range на xrange.
В 2-й версии python range() создаёт каждый список сразу, для создания генератора нужно применятьxrange(), в 3-й версии range == xrange (т.е. возвращает генератор).

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB