Pull to refresh

Comments 27

В разделе статьи про range() напишите, пожалуйста, что речь идёт про третий питон. Во втором питоне range(100*10000) создаёт огромный список в памяти.
Во втором питоне xrange() можно использовать.
тогда проще сразу написать xrange
Ну, справедливости ради, стоит сказать что xrange не будет работать в 3-й версии.
Исправил на xrange и добавил в статью разъяснения по этому поводу.
Да, это моя ошибка.
Имеется ввиду, конечно же, xrange для 2-й версии питона, который теперь проcто range в 3-й версии.
почему то после прочтения статьи я так и не понял зачем нужны Сопрограммы.

ой. Это должен был быть комментарий к статье, а не к вашему посту. Хотя первую ссылку я осилил, и все равно такие не понял — зачем?
Сопрограммы реализуют кооперативную многозадачность, т.е. подход, когда в рамках одного системного потока есть много мелких «воркеров», каждый из которых делает свою задачу и сам решает, когда вернуть управление, с помощью yield. Многие современные асинхронные библиотеки, потипу gevent и прочего, скрещивают эти легковесные «воркеры» с асинхронной моделью, реализуемой системными вызовами поллинга сокета (select/poll/epoll). В итоге мы как раз и имеем все эти «суперскоростные» и «держащие 100к коннектов» приложения.
То есть, мы получаем такую вот ограниченную (на первый взгляд) псевдопараллельность без использования multiprocessing, при этом имея разделяемые переменные, но не имея дедлоков?
Это достаточно сложная тема.

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

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

В рамках одного процесса тоже своя история, достойная отдельной статьи.
Касательно работы на одном процессоре — это мне известно, и я не понял, как это относится к моему вопросу?

Собственно, мой вопрос был такой:
Если мы используем multiprocessing, то мы имеем (небольшой) геморрой с передачей информации между процессами ОС, плюс оверхэд на переключение между процессами.
Использовать многопоточность в python смысла особого нет, даже при наличии нескольких процессоров/ядер — привет-привет, GIL!

А использование корутин позволяет на ленивых вычисления сделать имитацию параллельности, за счет того, что «тяжелые» операции выполняются по запросу, при это — без оверхэда многопроцессности, и без заморочек с GIL. Так?
Да, в целом так. Но при этом вы сразу упомянули и главный минус — тяжелая операция блокирует всю очередь корутин. Именно поэтому асинхронные приложения могут по производительности упираться, например, в работу с базой.

А еще — именно такого рода «зеленые потоки» используются в вещах потипу Stackless Python и некоторых прочих интерпретаторах и виртуальных машинах.
и все равно не понял. Кооперативная многозадачность это в Symbian где все ваше приложение работет в одном потоке. Я что то слабо вижу свзяь межде генераторами (yield) и кооперативной многозадачностью. Вернее я ее вообще не вижу.
И здесь всё в одном потоке: когда сопрограмма ждет новых данных, она делает (yield) и управление передается в другое место. При этом после передачи управления обратно генератору-сопрограмме (вместе с какими то данными) выполнение продолжится с того же места.

Это база для «нормальных» корутин, т.е. например шедулер нужно писать самому.
Ниже кидали ссылку на PEP 342, там все понятно расписано.
Почему-то не могу поставить плюс за ссылки, но спасибо, прояснили картинку лучше всего.
Для бо́льшей совместимости лучше писать next(gen), а не gen.next(). Дело в том, что в Python 3 заметили и устранили нестандартность метода .next: этот метод «магический» (точнее, управляет поведением встроенных возможностей языка) аналогично прочим вроде .__iter__, .__gt__; но при этом в Python 2 он пишется без обрамляющих __. В Python 3 метода next у итераторов нет, вместо него используется __next__. Таким образом,
In [92]: gen = (x for x in range(0, 100*10000))
In [93]: gen.next()

покажет AttributeError в Python 3, а
In [92]: gen = (x for x in range(0, 100*10000))
In [93]: gen.__next__()

— то же самое в Python 2. next(gen) будет работать везде.
uniq = []
for line in lines_generator:
if line not in uniq:
uniq.append(line)

Если задача в том, чтобы выбрать уникальные строки не соблюдая порядок, то код не оптимален, т.к. известно что сложность поиска элемента в списке это в среднем O(n) и чем больше будет становиться uniq, тем медленнее будет работать программа. Правильный вариант использовать множество:

uniq = set()
for line in lines_generator:
if line not in uniq:
uniq.add(line)

А еще проще так:

set(lines_generator)
Изначально у меня был комментарий, что лучше в этой ситуации использовать set(), а это всего лишь пример использования.
Но потом я решил, что очевидно.
А теперь, мне очевидно, что надо было лучше прорабатывать примеры.
А я в изменённом выше сообщении собирался сказать, что (x for x in range(N)) должно заменять на iter(range(N)). Правда, потом всё же сообразил, что iter(range(N)) со статьёй не вяжется. Но примеры действительно лучше бы выбрать такими, чтобы использование генераторов было уместным.
Как раз вчера смотрел примеры и документацию о реализации state machine и наткнулся на реализацию состояний через Co-routines.
Забавно, но моя вторая статья о python как раз про state machine.
То же была мысль реализовать через корутины, но потом решил пойти более привычным путём. Хотел опубликовать здесь, но решил что будет мало кому интересно.
Как по мне, в статье очень наглядный пример функции-генератора и yield. Спасибо, что внесли ясность.
Про yield тут уже очень хорошо расписали. Так что я старался сильно не заострять на нём внимание.
Но я рад что статья стала кому то полезной.
Читал ту статью. У вас понравился простой пример с чтением очень большого файла. Нормальная практическая задача, в отличие от (x*x for x in range(y)). А то насмотришься теоретических примеров, а где их применять — непонятно.
Сопрограммы создаются с помощью выражения value = (yield). В этом самом месте выполнение приостанавливается пока объект не будет вызван с аргументом coroutine.send(data). Затем выполнение продолжается с переданным значением, что бы сообщить об окончании вычисления мы используем метод .close(), который в свою очередь внутри сопрограммы возбуждает исключение GeneratorExit, которое мы можем перехватить. Пример как можно перехватить исключение:
def match(pattern):
        print('Looking for ' + pattern)
        try:
            while True:
                s = (yield)
                if pattern in s:
                    print(s)
        except GeneratorExit:
            print("=== Done ===")
Sign up to leave a comment.

Articles