Comments 31
Количество воды просто невероятное. По сути же, в статье не сказано ничего, просто продеманстрировано как много говнокода можно нагенерировать, если решать абстрактную проблему в вакууме при помощи микроскопа в качестве молотка
Самолёты-пауки? Что это? Неужели нельзя было просто оставить «тех.задание» исходного примера, который вы позаимствовали, без изменений? Зачем притягивать за уши «самолёты», на код обычного «паука», который скачивает страницы из интернета?
Зачем вы вообще используете Python? Вы его абсолютно не знаете. Я даже уже сомневаюсь, что вы вообще программировать умеете. Была бы у меня возможность, я бы отобрал у вас питон и не давал пока не
Я ещё в прошлой статье обратил ваше внимание на то, что вы используете в методах классов глобальные переменные. Но вы продолжаете писать абсолютную ересь, доводя её до абсурда. Теперь вы создали 3 одинаковых класса, которые используют внутри своих методов глобальный экземпляр ЭТОГО же самого класса. Как вообще у вас такая идея пришла в голову? Сами же потом оправдываете всё это незнанием языка и указателями (которых вообще в питоне, как языке программирования, нет). Прекратите писать о том, о чём не знаете. Изучите сначала язык, наберитесь в нём опыта, и только потом показывайте на публику свой код на нём.
И что вы вообще пытаетесь доказать своими статьям? Что автоматы — это какое-то лучшее решение чем async/await?
Только вот реализация async/await в питоне, в своей основе, сделана на генераторах. Генераторы — это просто синтаксический сахар для удобного написания итераторов. Итератор — это самый настоящий автомат. Генераторы позволяют проще писать и понимать код такого автомата, т.к. этот код не размазывается без необходимости по нескольким методам класса.
В итоге получается что код с async/await — это автомат. Просто он записан более понятным для людей образом без лишней «воды» в коде.
О боже! «Горшочек, не вари!».Давайте снизим накал эмоций…
Как вообще у вас такая идея пришла в голову?Вот так — взяла и посетила :)
Но, если серьезно. Я в прошлый раз Вас поблагодарил, т.к. Вы, действительно, мне помогли, подтолкнули к пониманию Python. Хотелось бы, чтобы мы такие приятные моменты приумножали :)
Скажу спасибо и в этот раз. За стимул :) Мне пришла в голову еще одна идея. Вот как она выглядит в коде:
import time
automaton = []
class PSleep:
def __init__(self, t, p_FSM): self.SetTime = t; self.nState = 0; self.bIfLoop = True; self.p_mainFSM = p_FSM
def x1(self): return time.time() - self.t0 <= self.SetTime
def y1(self): self.t0 = time.time()
def loop(self):
if (self.nState == 0): self.y1(); self.nState = 1
elif (self.nState == 1):
if (not self.x1()): self.nState = 4
class PBSpider:
def __init__(self, name, sleep):
self.nState = 0; self.bIfLoop = True; self.site_name = name; self.page = 1; self.n_sleep = sleep
def x1(self): return self.page < 4
def y1(self):
self.bIfLoop = False;
self.b_sleep = PSleep(self.n_sleep, 0)
automaton.append(self.b_sleep);
self.b_sleep.p_mainFSM = automaton[ind]
def y2(self): print(self.site_name, self.page)
def y3(self): self.page += 1
def y4(self): self.page = 1
def loop(self):
if (self.x1() and self.nState == 0): self.y1(); self.nState = 1
elif (not self.x1() and self.nState == 0): self.y1(); self.y4(); self.nState = 33
elif (self.nState == 1): self.y2(); self.y3(); self.nState = 0
class PAirplane:
def __init__(self, name, n):
self.obj = PBSpider(name, n);
automaton.append(self.obj)
PAirplane("Blog", 1)
PAirplane("News", 2)
PAirplane("Forum", 2)
start = time.time()
while True:
ind = 0;
while True:
while ind < len(automaton):
if automaton[ind].nState == 4:
automaton[ind].p_mainFSM.bIfLoop = True
automaton.pop(ind)
ind -=1
elif automaton[ind].bIfLoop:
automaton[ind].loop()
elif automaton[ind].nState == 33:
print("{:.2F}".format(time.time() - start))
exit()
ind += 1
ind = 0
Здесь я учел Ваши пожелания. Теперь уже ОДИН класс и глобальных переменных меньше. Открою даже свой небольшой секрет — на С++ я вообще не использую глобальных переменных. Их вред мне известен и понятен. И с повторами кода на С++ у меня тоже нормально ;)
По поводу знаний… Да, я не очень знаю Python. Наверное, в сравнении с Вашим опытом и знаниями так от слова совсем ;) И когда получаю дельные советы и помощь, то весьма благодарен…
И я не то, что сомневаюсь, а фактически уверен, что Вы не понимаете автоматы. Но как-то не укоряю Вас за это. Да и развеять на этот счет мое мнение можно достаточно просто — нарисуйте граф автомата генератора. Получится — тогда я извинюсь за свою самоуверенность.
Т.е. давайте от эмоций переключимся на деловой стиль общения.
А так еще раз спасибо. Хотя Вы и не подсказали, но Ваш «толчок», видимо, повлиял на идею, которая меня и посетила в этот раз. Хотелось бы узнать Вашу оценку ее.
Ох, поберегите мои нервы. Больно смотреть на ваше эгоистическое представление о красивом и читаемом коде. Вам может он и понятен. Но мне как человеку, который в первый раз его видит, трудно разбираться в этом месиве длинных однострочкиков с рандомным стилем именования сущностей.
Теперь у вас глобальная переменная automaton, и бесполезный класс PAirplane с сайд-эффектом в конструкторе (он меняет переменную automaton). Мне казалось что "класс ради класса" это особенность разработчиков на C# (там по другому нельзя) и в Java тоже любят всё делать классами, но даже в этом случае конструктор с сайд-эффектом — это привилегия новичков.
И ещё есть хитро спрятанная глобальная переменная ind, которая не первый взгляд просто какой-то счётчик внутри "event-loop-а", а на деле оказывается используется внутри метода класса PBSpider. Даю вам 5 балов за навык "минирования кода".
И ещё +1 бал за константы 4 и 33 для поля nState. Они мне нравятся, надо бы тоже почаще их везде пихать. Ещё я люблю 7. Я ведь могу в вашем коде поменять 4 на 7? Мне лично так понятнее будет, а то я 4 обычно для других целей использую.
Вы не добавили поле p_mainFSM в класс PBSpider, но при этом используете его в "евент-лупе". Но я вас понимаю — там же есть волшебная константа 4, которая железобетонно гарантирует, что мы сейчас имеем дело с экземпляром PSleep. Ведь все знают, что нельзя использовать 4 как значение состояния в других "автоматах" кроме PSleep.
В ваш код на C++ я даже не стал детально вникать. Для начала там где-то за кадром есть какой-то непонятный ВКП(а). Интернет предложил мне только "Всесоюзная Коммунистическая Партия".
Далее там какая-то шифровка в виде массивов двухсимвольных строк. И нет кода для его декодирования.
Ну и по мелочи — непонятно от куда взявшийся метод pFAwaitSleep->FCall(), которого нет в классе FAwaitSleep.
Что значит "блок схема генератора"? А какая блок схема у автомата? Абстрактного автомата.
Снимаю своё замечание про метод FCall — не заметил, что у класса есть базовый класс.
Вот еще
import time
class PSleep:
def __init__(self, t):
self.bIfLoop = True; self.p_mainFSM = 0; self.nState = 0;
self.SetTime = t;
def x1(self): return time.time() - self.t0 <= self.SetTime
def y1(self): self.t0 = time.time()
def loop(self):
if (self.nState == 0): self.y1(); self.nState = 1
elif (self.nState == 1):
if (not self.x1()): self.nState = 666
class PBSpider:
def __init__(self, name, sleep, p_evl):
self.p_EventLoop = p_evl; self.bIfLoop = True; self.p_mainFSM = 0; self.nState = 0;
self.site_name = name; self.n_sleep = sleep
self.page = 1;
def x1(self): return self.page < 4
def y1(self):
self.bIfLoop = False;
self.b_sleep = PSleep(self.n_sleep)
self.p_EventLoop.automaton.append(self.b_sleep);
self.b_sleep.p_mainFSM = self.p_EventLoop.automaton[self.p_EventLoop.ind]
def y2(self): print(self.site_name, self.page)
def y3(self): self.page += 1
def y4(self): self.page = 1
def loop(self):
if (self.x1() and self.nState == 0): self.y1(); self.nState = 1
elif (self.nState == 1): self.y2(); self.y3(); self.nState = 0
elif (not self.x1() and self.nState == 0): self.nState = 666
class PAirplane:
def __init__(self, name, n, p_evl):
self.p_EventLoop = p_evl
self.obj = PBSpider(name, n, self.p_EventLoop);
self.p_EventLoop.automaton.append(self.obj)
class PEventLoop:
def __init__(self): self.automaton = []; self.ind = 0
def add(self, p_tsk):
self.automaton.append(p_tsk);
def run_until_complete(self):
while True:
if len(self.automaton) != 0:
while self.ind < len(self.automaton):
if self.automaton[self.ind].nState == 666:
if (self.automaton[self.ind].p_mainFSM != 0):
self.automaton[self.ind].p_mainFSM.bIfLoop = True
self.automaton.pop(self.ind)
self.ind -= 1
else:
self.automaton.pop(self.ind)
self.ind -= 1
elif self.automaton[self.ind].bIfLoop:
self.automaton[self.ind].loop()
self.ind += 1
self.ind = 0
else: break;
start = time.time()
evl = PEventLoop()
PAirplane("Blog", 1, evl)
PAirplane("News", 2, evl)
PAirplane("Forum1", 3, evl)
PAirplane("Forum2", 3, evl)
PAirplane("Forum3", 3, evl)
PAirplane("Forum4", 3, evl)
evl.run_until_complete()
print("{:.2F}".format(time.time() - start))
exit()
Здесь уже учтено:
1. Заключительное состояние одно и номер его «тьфу-тьфу» — 666
2. Введен класс PEventLoop, содержащий все что нужно для управления процессами-автоматами, т.е. список автоматов — automaton и их индексация — ind.
3. Цикл работает до тех пор пока не отработают ВСЕ процессы (раньше рубилось по первому закончившему работу)
4. Печать времени вынес за цикл (а то, действительно, неудобно как-то ;))
Немного напрягает меня передача ссылки на объект PEventLoop, но пока так.
Работает так, как надо — параллельно. В последнем приведенном примере при увеличении числа процессов (а времена у них замечу тоже разные) время их работы не увеличивается. Класс!
Что значит «блок схема генератора»? А какая блок схема у автомата? Абстрактного автомата.
Я сказал блок-схема? :( Я попросил граф автомата. Автомат имеет несколько типовых способов описания — граф, таблица переходов, матричное описание… Блок-схемы среди них НЕТ. Я рисую обычно граф, как наиболее понятную и известную форму (см. графы в статье).
Если, как Вы утверждаете, генератор это автомат, то «пли-з-з-з» его граф. Он его должен иметь по определению. Как-то так… :)
А Вам еще раз спасибо! Вы меня стимулируете. Так, глядишь, и питон освою хоть немного;)
А то все тут некоторые — «минусы-минусы-минусы» Они ж мне, как слону дробина! :) Тьюфу на них (и на минусы, кстати, тоже)!:) Не интересно :(
Говоря про "абстрактный" автомат, я типа намекнул, что неплохо бы добавить конкретики. Какого именно генератора? Что он должен делать? А не просто "граф генератора", как будто у любого генератора и автомата есть граф по умолчанию.
Я могу показать вам код sleep в виде генератора. Думается мне что граф у него точно такой же как у вашего PSleep, зато читается и понимается в разы проще, и кода у него сильно меньше:
import time
def sleep(secs):
end = time.time() + secs
while time.time() < end:
yield
# Имитация примитивного евент-лупа (без евентов)
queue = [sleep(10), sleep(5)]
while queue:
iterator = queue.pop(0)
try:
# То же самое, что вызов метода .loop() у ваших автоматов
next(iterator)
# Возвращаем "корутину" в конец очереди
sleeps.append(iterator)
except StopIteration:
# "Автомат" завершил свою работу, не возвращаем его в очередь
pass
А вот так он будет выглядеть если его сделать без "сахара":
class Sleep:
def __init__(self, secs):
self._end = time.time() + secs
def __next__(self):
if time.time() >= self._end:
raise StopIteration
queue = [Sleep(10), Sleep(5)]
...
Согласитесь — это практически тоже самое, что ваш PSleep. Можете даже вынести проверку условия в отдельный метод и назвать его x1(), если вы пишете код лично для себя.
Опечатка в коде. Вместо
sleeps.append(iterator)
надо
queue.append(iterator)
Просто по отдельному процессу-задержке судить сложно да и, думаю, нельзя.
У автомата есть состояния. Они имеют конкретные имена. В каком состоянии находится процесс можно подсмотреть со стороны и засинхронизироваться с ним. Как с этим?
У автомата есть дискретное время, которым можно управлять, замедляя, ускоряя процесс. Здесь как? И речь даже не о времени отдельного автомата, а о дискретном времени их множества, т.е. о сети автоматов…
Вот что вызывает вопросы, когда говорим об автоматах…
Как я понял, у вас автоматы напрямую взаимодействуют с неким внешним "планировщиком". Как минимум добавляя в него новые автоматы, результат которых им нужен.
В случае с корутинами предполагается, что сами они не работают напрямую с евент-лупом, а просто возвращают некий future-like объект (или promise). И евент-луп не вернёт управление исходной корутине пока не будет завершено выполнение этой future (а если точнее, то возврат управления — это просто callback прописанный внутри future). Future — это по сути "состояние автомата", которое имеет как минимум три значения: выполняется, завершено, ошибка. Переход из одного состояния в другой выполняется путём "внешнего воздействия" на future. И в него можно добавить callback-и на завершение и ошибку. По моему это примерно тоже самое, что у вас делается с помощью pFAwaitSleep->FCall()
.
И мне почему-то кажется, что future — это результат, который получился из попыток унифицировать и упростить работу с автоматами в том виде как это реализовано у вас. Евент-луп работает с future-объектами, а что там за ними стоит, какие калбяки они вызывают — это не его забота.
Фактически вся внутрянка, на которой работает асинхронный код — это всё автоматы. А за счёт специальных языковых возможностей (async/await), у программиста есть возможность писать короткий, легко читаемый код в "синхронном" стиле. В принципе можно писать код без async/await, и явно оперировать с Future-ами. Но это не удобно и порождает сильно вложенный код и callback-hell.
Согласен, что возможность отследить состояние автомата в явном виде — это удобно в некоторых случаях. При желании можно и в корутинах отслеживать статус, но он не будет обязательным для работы самой корутины, а просто как некая мета-дата. Но обычно в этом случае используют "примитивы синхронизации" (которые, наверное, тоже автоматы). Они позволяют синхронизировать разные "задачи" без использования "магических констант" и нарушения инкапсуляции, когда код снаружи "автомата" почему-то знает о его внутренних статусах.
PS: На самом деле меня не коробят ваши автоматы в C++. Пишите на нём как вам удобно. Больше всего меня задело, то что вы не зная Python пишите на нём совершенно некорректный код. А потом ещё и делаете какие-то выводы на его основе, и даже местами сравниваете с C++. Не надо так. Пишите на C++, и мне будет всё равно. Я наверное даже читать не буду, т.к. я давно на нём не пишу и не планирую в будщем.
Фактически вся внутрянка… — это всё автоматыВ этом есть 100%-я уверенность, что это не так. Иначе это как-то вылезло бы в наружу и уж я бы их распознал.
А за счёт… есть возможность писать короткий, легко читаемый код в «синхронном» стиле.Я не буду спорить, что он читается легче. Когда-то и сам так писал, да и сейчас методы автомата пишутся в этом же стиле. Это норм. А вот на уровне самого алгоритма этот «синхронный» стиль меня не устраивает. В двух словах не скажешь. На эту тему все мои предыдущие статьи.
Согласен, что возможность отследить состояние автомата в явном виде — это удобно в некоторых случаях.В автоматном программировании (АП) это элементы естественного состояния программы. И потребоваться могут в любой момент. Мне не надо даже задумываться — беру и пользуюсь. А все эти «примитивы» это те же «костыли», о которых я пишу. Вот с чем не хочется связываться.
PS: На самом деле меня не коробят ваши автоматы в C++.Главное, что они не коробят меня :)
… не зная Python пишите на нём совершенно некорректный код.Ну, не знаю — узнаю. Уже сейчас я больше знаю, чем пару дней назад :) По поводу некорректности. Если питон не выдает ошибок, то в чем тогда некорректность? В корявости? Дело наживное. Последний код намного приличнее, чем первый из статьи. Еще можно что-то скрыть через наследование, как сделано в С++. Но есть ли оно у объектов питона? Думаю в Вашей, с чьей-то помощью или сам, но разберусь. Если нет (наследования), то по мне и так сойдет. Питон пока не мой основной язык. Но бросать его пока не планирую. Жизнь устаканит… Тут вот она еще один язык подсовывает :( Нежданно… Освоим… В дополнение к С++ и питону;)
Когда я был маленький, меня били за переменные с незначащими именамиНу, за такое, может, и надо. Но только надо знать, конечно, меру… :)
У меня нет однобуквенных имен переменных. Если речь об «иксах» и «игреках», то, во первых, они пронумерованы. Во-вторых, имеют следующий вполне конкретный смысл. У автомата есть множество входных каналов и выходных. Входные — иксы, выходные — игреки. Каждый их номер — это номер соответствующего канала. На структурном уровне — это также «черный ящик» с множеством каналов…
Когда-то я тоже хотел подобные имена сделать более одушевленными. Но потом отказался от этой идеи. Это было сделано несколько вынужденно, но теперь уверен, что так оно даже лучше. Мне, как минимум. Может, потому, что маленьким меня за подобное не наказывали ;)
А за оценку идей — спасибо!
А если каналов будет 20? А если 50? И вы впервые видите этот код, а вам надо оперативно что-то исправить. А представитель заказчика (как назло) объясняет свою проблему обычными словами, а не блок-схемами на которых указаны правильные иксы и игреки. А блок-схему для этого кода почему-то не нашли (наверное автор её забрал с собой, когда увольнялся).
И конечно же автомат достаточно сложный. Его код минимуму на 3 экрана растягивается по высоте. Одним взглядом сразу не окинуть, и не увидеть что метод y25()
вызывается не по тому условию. Там почему-то используется x23()
, а надо x24()
(наверное дети какие-то писали, которые не знают прописные истины).
Но это я слишком сильно далеко заглянул в будущее. Вы в этом время скорее всего ещё только запускаете дебагер, что бы по шагам разобраться в том как работает этот автомат, и у вас впереди есть один или даже два интересных дня, что бы это сделать. Что-бы заново нарисовать блок-схему и уже в ней найти ошибку, т.к. просто смотря на код её не увидеть.
А если каналов будет 20? А если 50?Зашито до 32-х. Дальше — паника! :) Но вот сколько использую, но этого числа — за глаза! По входу обычно до 7-10, по выходу может максимум раза в два-три раза больше…
Одним взглядом сразу не окинутьЛогика — тьфу — это таблица переходов. А это десять плюс-минус строк. Рисуем — пять сек. Методы, как правило, небольшие. Классы, как видите, как правило, тоже не очень большие. Отлаживаются — на раз. Дальше только используй, т.к. все «заковано» в библиотеку отлаженных и проверенных не раз процессов. Отладка — все просто и понятно, как ясный день. Потоков — нет (тьфу-тьфу не накаркать бы)! Красота!
Короче — автоматная технология проектирования «панимашь»! Все отточено и выверено годами жесткой практики :)
Хорошо, когда всё "обычно" и не выходит за рамки того "как правило". Но в проектах, над которыми работают несколько человек, которые при этом периодически меняются, лучше на это не надеяться. Кто-нибудь обязательно запилит автомат, который упрётся в лимит "32", и засунет в него всю бизнес-логику приложения. И будет он занимать 1000+ строк.
Хотя это конечно ваше дело, видимо вы или совсем не работали в команде. Или делали небольшие приложения, которые вы лично и поддерживали, и вам не приходилось каждые полгода объяснять новым членам команды, что означают все эти иксы и игреки. И в каком именно игреке надо искать код, который нужно поправить для решения задачи. Можно даже найти плюс в таком подходе, не надо ломать голову над одной из сложнейших задач программирования — придумывание названий.
Для меня же ваш код выглядит как результат работы обфуксатора или минимайзера. Там примерно такие же названия функций из 1-2 символов.
Кто-то просто так не запилит. Если упрется, то это или исключительный случай или чел просто тупит и не понимает как процесс разбить на множество небольших автоматов. Дальше так: не может — научим, не хочет — заставим. Если конечно не безнадежный случай ;)
Вот даже Вы сказали — сложнейшая задача. Здесь она упрощается. Конечно, нужно знать, что значит каждый вход/выход автомата. Но это загоняется не в имя предиката/действия, смысл которого чуть погодя совсем забывается, а в осмысленный комментарий к нему и в правильное документирование программы, которое в случае автоматов более осмысленно, чем для блок-схем.
Есть плюсы и минусы в отделении логики от текста предикатов. Но плюсов больше. Особенно, когда надо разобраться/вспомнить как оно/она работает ;)
О! Чуть не забыл. Кроме питона есть и MATLAB, а в нем — Stateflow — автоматное проектирование. Признано многими и крутым и весьма эффективным. Используется по всему миру и, думаю, в проектах любой сложности.
Ну а таблица? Надо посмотреть. В С++ у меня таблица переходов. Это очень удобно и по большому счету правильнее, т.к. в точности соответствует таблице переходов автомата.
Что-то мало минусов поставлено статье на мой взгляд.
1) Меня категорически не устраивают существующие технологии программирования. У меня автоматное параллельное мышление. По-другому уже не то, что не могу, — не хочу. Вот такой я «иносранец».
2) Шайтан попутал :) Смотрю народ от корутин тащится, которые (сопрограммы) я отринул много лет тому назад. И чего они в них нашли, думаю? ;) Вот так на питон и вынесло. Поначалу, чуть было не зацепило за Котлин, но… не сложилось как-то.
А С++ ни куда не денется, видимо. Теперь хоть немного в теме, что творится помимо него. А то уж совсем было скучно стало… Чтобы совсем не закиснуть решил задокументировать свои идеи и народ повеселить ;) Правда, тут уже наклевывается веселуха, т.к. что «горшочек», похоже, будет варить уже не так активно. Накликали, похоже… ;)
Мир без корутин. Костыли для программиста — asyncio