Нейрон мне в элерон: суррогатные модели
Привет, Хабр! Мы продолжаем серию статей о суррогатном моделировании, на этот раз расскажем, как мы применили нейросети при создании сложных технических систем – приспособили GRU в качестве суррогатной модели для испытаний. Реальный кейс для клиента в рамках реального проекта.
Итак, давайте поставим нейросети обучение на службу проектировщику. Полетели!
Photo by Jerry Zhang on Unsplash
Какой чудесный вид открывается через иллюминатор авиалайнера, не правда ли… Элероны, спойлеры, интерцепторы, предкрылки, закрылки…
Собственно, элерон, о котором идет речь в нашей статье – небольшая отклоняемая поверхность на задней кромке крыла, ближе к законцовке. Элеронов обычно два, их основная роль состоит в управлении креном самолета. Как они это делают? В режиме управления креном, левый и правый элероны отклоняются дифференциально, а поскольку они расположены далеко от фюзеляжа, даже легкое отклонение создает заметный крутящий момент, которой и кренит самолёт.
Arne Nordmann (user:norro), 2006. Собственная работа, Illustration based on the illustration Image:PlaneWing.png of Piotr Jaworski (PioM)
В основном, все эти отклоняемые поверхности нужны летательному аппарату для того, чтобы изменять подъемную силу и лобовое сопротивление и, в конечном итоге, чтобы стабилизироваться и осуществлять навигацию. Воздух может оказывать их движению значительное сопротивление, поэтому подвижные поверхности авиалайнера «с дистанционным управлением» приводятся в движение мощными гидравлическими приводами.
Слева гидропривод элерона 737, за фотографию огромное спасибо lx-photos: https://lx-photos.livejournal.com/337851.html
Так что, если заглянуть внутрь ниши шасси, мы увидим другой, не менее прекрасный инженерный ландшафт: гидробаки, фильтры, патрубки, насосы… Но чего мы не увидим, так это нейросетей… Интересно, откуда тут возьмутся MATLAB, Python и фреймворки машинного обучения?
«А давайте протестируем нашу программу управления на паре триллионов сценариев» скажет кто-то… и все резко изменится.
Или, имея только аэродинамические характеристики, сгенерировать код для стенда имитатора, не рисуя никаких промежуточных схем. Суррогатные модели находят свое применение как в модельно-ориентированном проектировании, так и в программно-аппаратном тестировании, но есть одно «но».
Нужны модели. Много моделей.
Включайте аэродинамические трубы, несите мешки с песком… На всех этапах модельно-ориентированного проектирования нам нужны детальные и точные модели. Как минимум, они нам пригодятся для следующих применений:
При проектировании – чтобы найти оптимальную конфигурацию системы, протестировав модель в большом количестве сценариев;
При разработке – компоненты нужно согласовывать между собой, для этого нужно прогнать много интеграционных тестовых сценариев;
При развертке – из моделей моделей можно генерировать код компонентов и настраивать среду на основе испытаний;
При валидации и верификации – некоторые критические компоненты придется протестировать на всем диапазоне входных значений и их комбинаций.
Главная проблема, которая у нас возникнет – как запустить модель несколько триллионов раз, в пространстве всех комбинаций параметров, за разумное время, да еще и на имеющихся компьютерах?
Суррогатные модели, на помощь!
Сложные технические системы могут быть описаны очень разными способами. Как минимум, в модели будет система из обычных и дифференциальных уравнений, которые будут как-то связаны между собой, например, через вычислительный граф (типичная модель Simulink) или расчетную сетку (модели в конечных элементов для газовой динамики и пр.).
Как вычислительные графы, так и модели в конечных элементах могут выполняться очень медленно… Гораздо медленнее реального времени. Запуск во всех нужных сценариях может занимать дни, возможно даже недели.
Чтобы ускорить эксперименты над моделью такой системы, нужно как-то ускорить ее выполнение (симуляцию).
На замену детальным моделям можно создать суррогатные. Их основная задача – скоростное выполнение, небольшой объем занимаемой памяти и соблюдение заданного порога ошибки относительно детальной модели.
Суррогатные модели на замену детальной симуляции, https://habr.com/ru/company/etmc_exponenta/blog/663620/
Чтобы подробнее ознакомиться с мотивацией и приемами суррогатного моделирования, у нас есть целая серия материалов:
А теперь, к практике – о том, как мы в динамической модели отдали часть расчетов нейронной сети, и что из этого вышло.
Модель системы управления элероном
Наша базовая модель была представлена вычислительным графом в Simulink. Перед нами модель привода (2) и модель его электронного блока управления (1).
Модель блока управления (ECU) и объекта управления (гидравлического привода)
Входные сигналы – нагрузка от элерона и сигнал управления на входе электронного блока. А на выходе – положение штока привода, оно же выступает в качестве сигнала обратной связи для блока управления.
Предположим, что мы отлаживаем параметры ЭБП. Для этого нам нужна модель привода, которую можно запускать много раз в разных условиях. Ее мы и будем заменять на суррогатную модель. Сценарий нагрузки у нас не меняется, значит – либо моделируем небольшие отклонения, либо ждем более обширного кейса, ведь это только начало нашей серии рассказов об практике применения суррогатных моделей.
Входят нейросети!
Цель этого проекта заключалась в том, чтобы заменить модель привода на рекуррентную сеть GRU (управляемый рекуррентный блок).
Один классический слой GRU рассчитывается по такой системе уравнений:
Собственно, в отличие от нейронов обычной нейросети, в GRU-нейроне сразу три функции активации. Вектор h (hidden state) хранит состояние слоя GRU. Он используется нейросетью сразу и как вектор выходных значений, и как память, из которой нейрон может доставать что-то, выученное много шагов назад. Векторы z и r – просто внутренние данные каждого нейрона. Ну, и матрицы W – это наборы весов, которые сеть должна выучить (смещения, судя по всему, уже занесены во входной вектор; нотация отсюда).
Как видно, это весьма несложная модель – несколько нелинейностей и перемножений. За компромисс между точностью и скоростью выполнения отвечает в основном количество нейронов. За скорость обучения и глубину выученных зависимостей отвечает длина примеров в обучающей выборке.
Кроме рекуррентного слоя, нам понадобится слой пакетной нормализации (BatchNorm) для стабилизации и ускорения обучения нейронной сети. На выходе также располагается полносвязный слой для предсказания нового положения штока нашего привода (например, на случай нескольких выходных переменных).
И вот, как здорово выглядит Simulink-модель после добавления обученной нейросети.
Суррогатная модель объекта управления в Simulink
Сеть можно обучать в MATLAB Deep Learning App или в Python (и импортировать через ONNX), поэтому тут легко учесть все пожелания и создать удобную и гибкую методику обучения.
При каждом вызове нейросеть обновляет свое состояние и рассчитывает следующую выходную переменную – положение штока.
Результаты моделирования
На этом раннем этапе проекта мы просто хотели применить довольно классическую процедуру обучения GRU, поэтому договорились о довольно грубых параметрах оценки: при расчете с заданным шагом 0.001, относительная ошибка не должна превышать 10%.
Эти параметры сеть выбивает легко и просто. Например, вот так сеть прогнозировала поведение нашего объекта в ходе колебательных процессов (шаг за шагом):
Участок моделирования с резкими колебательными процессами Участок моделирования с резкими колебательными процессами
Мы видим пару систематических ошибок – в момент после резких скачков наблюдаются самые большие отклонения, но в целом сеть предсказывает сигнал довольно хорошо.
Заключение
Нейросети все еще очень редко применяются в промышленности, проблемы остаются по части интерпретируемость полученных моделей и доступности самого подхода моделирования нейросетями. Ведь вы должны быть в состоянии попробовать несколько вариантов моделей, не прибегая к организации НИОКР.
Поэтому нам так важно делиться с вами своими соображениями и выслушивать ваши мысли.
Какие преимущества имеют нейросети как суррогатные модели?
Легко управлять скоростью и точностью модели
Чем больше нейронов в слое, тем медленнее (и точнее) будет работать нейросеть;
GRU слой нейросети позволяет сэкономить память по сравнению с LSTM-слоем за счет меньшего числа параметров;
Нейросети легко можно распараллелить
Но только те, которые работают без обратной связи;
В пакетном режиме вычисление всей кривой происходило бы мгновенно.
Рекуррентные нейросети, пожалуй, не стоит рассматривать как первую модель в списке:
если у вас в целом небольшая модель, которую не особо нужно ускорять (как было в нашем случае);
если модель должна работать в потоковом режиме;
если в динамической системе есть обратная связь.
Ведь мы не избавились от большей части вычислительного графа, так что – чтобы усилить преимущества, можно спланировать другую процедуру обучения, если это не усложнит эксперименты на стороне заказчика. Но конкретно в задаче с GRU остановились на достигнутом и будем готовить для вас новые результаты по более обширным кейсам, чтобы мы все могли обсудить работу нейросетей в суррогатных моделях.
В целом, мы уверены что внедрение суррогатного моделирования в вашей команде может перевернуть весь рабочий процесс. Или уже перевернул? Расскажите!
PS: Основную работу по кейсу выполнил Игорь Матвеев, инженер Экспоненты (@Igorec_matveev). 👏
Недельный геймдев: #77 — 10 июля, 2022
Из новостей: доступна бетка PixelBasher, Unity уволила 4% сотрудников, Nexters уволит 235 человек, Godot 4 добавляет режим Movie Maker, Arm представила свой первый графический процессор с аппаратной трассировкой лучей для Android, ЕС обязала Apple и Google разрешить установку приложений со сторонних площадок.
Из интересностей: круглый стол о перспективах российской игровой индустрии; игра, разошедшаяся тиражом в 500 тысяч копий, но вы про неё даже не слышали; встраивание JS в свой игровой движок.
Обновления/релизы/новости Nexters уволит 235 человек — почти четверть командыУвольнения затронули специалистов каждого отдела или подразделения в России, Армении, Грузии, на Кипре и в других странах.
Поо словам сооснователя и гендиректора Nexters Андрея Фадеева, в 2022 году компания столкнулась с «рядом негативных внешних факторов».
Godot 4 добавляет режим Movie MakerMovie Maker предназначен для записи с фиксированной частотой кадров, что идеально подходит для создания высококачественных игровых трейлеров или фильмов в стиле машинимы.
Увольнения в UnityКомпания объявила об увольнении 4% сотрудников (чуть более 200 человек). Это произошло вскоре после успешного IPO в конце 2020 года, за которым последовали некоторые крупные приобретения, включая покупку Weta Digital за 1,6 миллиарда долларов. В последние месяцы акции Unity резко упали, в настоящее время они торгуются по цене менее 40 долларов за акцию, что значительно ниже максимума в 200 долларов.
Доступна бетка PixelBasherРазработчик игр Ричард Лемс объявил, что теперь доступна бета-версия PixelBasher.
Этот инструмент для китбэшинга призван дать художникам возможность с легкостью создавать пиксельную графику.
Вышла новая версия Tiled 1.9, редактора карт с открытым исходным кодомВ выпуске представлен полностью переписанный алгоритм автомеппинга с увеличением скорости в 20-30 раз.
Arm представила Immortalis — свой первый графический процессор с аппаратной трассировкой лучей для AndroidПостроенный на архитектуре Arm GPU Valhall, Immortalis-G715 предлагает повышение производительности на 15% по сравнению с графическими процессорами Mali премиального и суб-премиум-класса предыдущего поколения.
ЕС обязала Apple и Google разрешить установку приложений со сторонних площадокПомимо этого, DMA требует от компаний прекратить принуждать разработчиков к использованию только их платёжных систем.
Cocos Creator получил поддержку Nintendo SwitchCocos Creator для Nintendo Switch с июля стал доступен всем разработчикам Nintendo Switch в разделе промежуточного программного обеспечения на портале разработчиков Nintendo.
Rust GameDev #35: июнь 2022 Объёмный туман появятся в Godot 4.0В дополнение к существующему необъёмному туману в Godot 4.0 представлен новый тип тумана: Volumetric Fog. В версии 4.0 добавили ещё FogVolumes. Это позволит пользователям динамически размещать туман и управлять сложными эффектами с помощью шейдеров.
Blizzard потребовала остановить разработку Warsmash, улучшенного движка Warcraft IIIАвтор создавал проект с открытым кодом и никак не монетизировал его.
Халява/раздачи/бандлы/курсы Бесплатное руководство по анимациям от Epic GamesКомпания выпустила The Animation Field Guide — бесплатную 120-страничную электронную книгу о производстве в реальном времени, включающую тематические исследования работы Sony Pictures Imageworks и DNEG Animation.
Pilgway выпустили бесплатную версию 3DCoatБесплатная версия лицензирована для коммерческого использования и содержит все инструменты скульптинга из 3DCoat, но с уменьшенным количеством полигонов экспортируемых моделей (до 40 000 треугольников при экспорте).
Более 1200 бесплатных наборов PBR-текстур с LotPixelТекстуры охватывают ряд реальных материалов и лицензированы для коммерческого использования.
Пак ассетов Tiny Dungeon Бесплатный контент для Unreal Engine за июль 2022В этом месяце Epic Games работали с сообществом GameFromScratch, чтобы определить продукты Marketplace, которые принесут пользу сообществу разработчиков.
Интересные статьи/видео «Давайте считать, что геймдева в России сейчас нет»: круглый стол о перспективах российской игровой индустрииМнения разработчиков, издателей и инвесторов о текущем положении вещей: Алекс Ничипорчик, Евгений Кистерев, Илья Еремеев, Максим Фомичев.
Эта игра разошлась тиражом 500 тысяч копий, но вы про неё даже не слышалиHydroneer от Foulball Hangover — песочница от первого лица с майнингом и строительством баз, которая получила колоссальные 12 000 отзывов в Steam с момента её дебюта в мае 2020 года. И в прошлом месяце игра набрала около 2000 отзывов, благодаря большому обновлению с многопользовательским режимом на разделённом экране.
GameDiscoverCo связались с создателем Максом Хайоном для интервью. И он подтвердил, что первая игра, которую он создал после окончания университета в Великобритании, разошлась тиражом в 500 000 копий в Steam (отношение количества отзывов к продажам в 40 раз больше).
Встраивание JS в свой игровой движокПисать на С++ игры долго и дорого, но при этом по перфомансу получается хорошо. Но далеко не всё в играх, требовательно к производительности, особенно 2D.
Скрипты — решение всех бед. И код писать проще, и обновлять приложение можно через дозагрузку.
Как Sony Pictures Imageworks создала триллер в реальном времени для Love, Death + RobotsКоманда из 45 художников создала шесть фотореалистичных человеческих персонажей с прекрасно выполненными анимациями боя, диалогами и эффектами; четыре разных окружения, от афганских гор до логова дьявола; и даже Ктулху всего за четыре месяца производства, несмотря на то, что у команды не было подобного опыта ранее.
Как рисовать изометрию: правила, советы, ошибкиИзометрия — один из самых популярных вариантов отображения игрового мира, особенно на рынке мобильного геймдева. Smirnov School пытаются разобраться, как с ней работать.
Погружаемся в генеративное искусствоВедущий художник по материалам Ишан Верма поделился обширным обзором генеративного искусства, продемонстрировал бесчисленное количество примеров красивых сгенерированных произведений и объяснил, как они были созданы, рассказал о различных ИИ для преобразования текста в изображение и обсудил, что рост ИИ означает для художников.
Искусственный интеллект. Следующий уровень в «игре»Один из тех самых постов, который интересен не сам по себе, а своими комментариями.
Знакомство с Tentacular, хаотичным кайдзю от FirepunchdИнди-разработчики Саймон Кубаш и Лука Скарамуццино присоединились к Unity на GDC, чтобы обсудить итеративный процесс проектирования, лежащий в основе их игры-песочницы для виртуальной реальности.
Плейтесты: почему это стоит каждой копейкиИсследовательская группа 80 Level представила новый отчёт, посвященный тестированию, его важности для игровой индустрии, типам тестирования и тому, как его можно использовать для улучшения маркетинга.
Грабли, слезы, два кранча — горький опыт студии perelesoqПоследние два с половиной года команда делала игру про девочку на войне — Torn Away. Ещё недавно они готовились к релизу, запланированному на конец марта 2022 года. А потом наступил февраль.
Отличный дизайн уровней — это работа всей студииВ этом докладе с GDC 2021 года Дана Найтингейл из Arkane Studios Lyon рассказывает о важном вкладе всех членов команды студии в создание игр с отличным дизайном уровней, а также о том, как дизайнеры уровней могут сообщать этим командам о своих потребностях, чтобы все могли помогать друг другу делать свою работу лучше.
Оптимизация производительности на мобильниках с помощью Adaptive Performance 4.0Unity 2022.1 предоставляет ранний доступ к новым функциям, которые будут включены в следующий выпуск с долгосрочной поддержкой (LTS). Adaptive Performance 4.0 поставляется с некоторыми новыми дополнениями для расширения масштабирования вашего приложения и повышения его общей производительности.
Виртуальное производство при работе над рекламой Diablo ImmortalЧтобы воплотить концепцию Battle at Home Screen в жизнь, студия Impossible Objects применила ряд передовых методов кинопроизводства, включая создание виртуальных окружений, 3D-ассетов, предварительную визуализацию в реальном времени, гибридную съёмку в реальном времени и сложные анимации.
Как рисовать пиксель-арт: программы, пайплайн, советыSmirnov School разбираются в процессе создания пиксель-арта: от необходимых программ до подробного пайплайна.
Разработка простого инструмента для создания листвы в HoudiniТехнический художник Йомна Сахаб рассказала, как и почему был создан инструмент Foliage Tool, поведала о рабочем процессе, стоящем за проектом, и поделилась некоторыми советами и рекомендациями для начинающих художников Houdini.
Прозрачность, не зависящая от порядка, часть 1В статье показано, как использовать попиксельные связанные списки для реализации прозрачности, не зависящей от порядка.
Дополнительно обсуждаются ограничения и рассматриваются альтернативы для уменьшения накладных расходов на память.
Симуляция косяка рыб с помощью Niagara и UnrealХудожник использовал модифицированную версию Boids для настройки симуляции.
Стоит ли идти на курсы геймдизайна и как отличить плохие курсы от хорошихАлиса Высотина порассуждала на тему онлайн-обучения профессии геймдизайнера: возможно ли это вообще и как не облажаться с выбором школы.
Физические единицы для источников светаВ статье обсуждаются различные физические единицы, используемые для описания света. Даются пояснения для каждой единицы и отношения между ними.
Статья включает в себя удобную сводную графику в качестве шпаргалки.
Путь анимаций Mush-Mush & the MushablesLa Cabane ушла в реалтайм, обновив пайплайн своего анимационного сериала, номинированного на премию Эмми, Mush-Mush & the Mushables, и с помощью Unreal Engine уменьшила время рендеринга на 90%.
История создания игры в одиночкуИз знаний были только какие-то остатки основ программирования на Турбо Паскале. О своём пути автор рассказал на Хабре.
Советы по рисованию материалов: золота, дерева, мягкой игрушки и драгоценного камняКорейская художница SAPPI рассказывает об особенностях материалов и поэтапно показывает их рисование.
Smirnov School перевели материал для DTF.
Несколько систем меню и дизайн пользовательского интерфейса в APICOРазработчики TNGineers создали пользовательский интерфейс с перетаскиванием, чтобы дополнить хитрый характер игры.
Создание своей первой в жизни игрыДенис Вебер решил примерить на себя роль разработчика и попробовать создать свою первую в жизни игру.
Ni no Kuni: Cross Worlds раздвигает границы того, чем может быть мобильная MMORPGЧтобы увидеть, как команда добилась этих невероятных результатов, Epic Games обратились к Netmarble, чтобы узнать, как различные инструменты Unreal Engine, такие как Sequencer, Animation Blueprints, Material Editor, Live Coding и другие, сыграли важную роль в разработке.
Игровой движок Creation Engine — как всё начиналось? Туториал по созданию классных кустов в Blender Управление сообществом разработчиков игр с помощью GitHub ActionsA Little Game Called Mario — проект с открытым исходным кодом. Узнайте, как коллектив использует GitHub Actions для управления этим замечательным маленьким сообществом.
Как сделать хитовую инди-метроидваниюGameDiscoverCo узнали об успехе Haiku The Robot, которая вышла 28 апреля, от разработчика Джордана Морриса. Игра была в топ-30 Steam за апрель по количеству обзоров, запущена с 37 000 в списке желаемого Steam, продано около 15 000 копий за первые шесть недель. Для соло-инди хороший результат.
Что такое система предупреждений и как она работает в Witchfire? Разное Стилизованный визуальный эффект атаки, созданный в Unreal EngineDDDDDD рассказал про проект на ArtStation.
Реалистичная симуляция ткани в Unreal Engine с помощью плагина uDraperОбтягивающая одежда также взаимодействует с мешем тела в режиме реального времени.
Плавная анимация дракона, созданная с помощью Maya, Megascans и UE5Рустам Терехов поделился рендером старого проекта на linkedin.
Район из Атаки Титанов воссоздан в UE5Томас Онго воссоздал родной район главного героя из известного аниме.
Оттенки стресса Реалистичный медведь, созданный в HoudiniМодель медведя использовалась в рекламе Super Bowl Doritos 2022 года.
Вдохновленный Dark Souls проект в Unreal Engine 5 и с помощью LumenХудожник использовал пакет Gothic Interior Megapack от Meshingun.
Основной свет — солнце/небо с объёмным туманом, риг персонаж с помощью ALS4.
19 способов сделать сокет-сервер на Python. Эволюционный подход. Часть 4. Сопрограммы в Python
Как известно, если хочешь что-то понять, найди сначала тот начальный момент, из которого это что-то появилось. Зри в корень, как говорил Козьма Прутков. А найдя корень, проследи всю его эволюцию до настоящего времени. То, как она протекала, и почему именно таким образом. Хотя если понимать не обязательно, а нужно только делать, то можно и не разбираться.
Поскольку асинхронность в Python реализована через сопрограммы, или корутины (coroutines), сопрограммы произошли из генераторов, генераторы появились из итераторов, а итераторы были созданы для перебора последовательности, то начнем с перебора последовательности и пройдем всю приведенную цепочку в обратном направлении.
Итерирование
Проще всего последовательность (в частности, массивы, или, как они называются в Python, списки — list ) можно перебрать, обращаясь к каждому элементу по индексу. Индекс всегда начинается с 0 и заканчивается длиной минус 1:
В данном примере алгоритм перебора (итерирования) элементов прямо встроен в функцию, и чтобы поменять алгоритм, нужно изменить саму функцию. Например, если мы захотим вывести элементы списка в обратном порядке, то нам придется создать новую функцию, где 3 из 4-х строк будут измененными, хотя назначение ее — вывод элементов на экран — остается прежним:
Итераторы
Для того, чтобы отделить алгоритм итерирования от основного назначения данной функции ( print(items[i]) ), введем новую сущность — итератор. Она предоставляет единый интерфейс для доступа к следующему элементу, а конкретная реализация итератора уже сама решает какой элемент в действительности нужно отдавать:
В результате у нас получается единственное назначение у функции — вывод элементов, а то в каком порядке они будут выводится — в прямом или обратном — зависит от объекта итератора, переданного ей в качестве аргумента:
Разработчики Python пошли дальше и ввели итераторы как одну из ключевых концепций языка. Так, метод next() становится магическим: __next__() , каждый объект с таким методом — итератором, а цикл for-in теперь автоматически вызывает __next__() при каждой итерации. Завершается цикл только тогда, когда итератор сгенерирует специальное исключение StopIteration . Вне цикла for-in получить следующие значения итератора (то есть вызвать метод __next__() ) можно специальной функцией next() :
Не правда ли такая запись ( for item in iterator: ) гораздо легче воспринимается и быстрее пишется, чем for (i=0; i < len(l); i++) item = l[i] .
Чтобы в цикле можно было использовать последовательности точно так же, как и итераторы, и в те и в другие добавляется еще один магический метод — __iter__() , создающий итератор. При этом цикл for-in всегда сначала вызывает __iter__() у переданного ему объекта, и только потом начинает перебирать элементы возвращенного итератора. Если уже был передан итератор, то его __iter__() возвращает ссылку на самого себя:
Таким образом, цикл for в Python работает только с итераторами. Если других языка итераторы — лишь один из допустимых паттернов проектирования, то в Python — это обязательная, неотъемлемая часть языка.
В итоге образуется новый тип итерируемых объектов (Iterable), в который входят и контейнеры элементов (Container), и итераторы (Iterator). Чтобы быть итерируемым, объект должен содержать метод __iter__() . А генераторы должны еще иметь и __next__() .
Типичный итератор списка будет выглядеть примерно так:
Генераторы
Однако, время от времени нам все же нет-нет да и нужно делать перебор индексов — чисел от 0 до n — 1 включительно. Для этих целей создается итератор, не проходящийся по элементам существующей последовательности, а генерирующий значения на лету:
С этого момента простой цикл for становится ненужным, так как он легко может быть заменен for-in + range(). Поэтому он и был удален из языка. Ведь согласно 13 пункту Дзена Python должен быть один и, желательно, только один очевидный способ сделать что-либо.
Итератор range() можно использовать и тогда, когда нужны и индексы, и сами элементы последовательности. Хотя в таких случаях удобнее использовать другой итератор — enumerate() :
Вот как range() можно реализовать:
В сущности, это уже не итератор, а генератор. Ведь он не перебирает (итерирует) элементы последовательности, а создает их на ходу. Генератор позволяет создавать элементы последовательности без необходимости хранить всю последовательность в памяти. В отличие от обычного итератора.
В Python понятие генератора выделилось из итератора в отдельную сущность языка. Внешне он выглядит, как функция, но от последней отличается присутствием инструкции yield . Наличие хотя бы одного yield говорит, что это уже не функция, а генератор.
Благодаря этому код из примера выше можно представить в более коротком и элегантном виде:
В отличие от функции вызов генератора не начнет выполнение внутреннего кода, а создаст объект генераторного итератора (generator iterator). Чтобы начать выполнение нужно начать этот итератор итерировать: поместить в цикл for-in или вызвать функцию next() :
Генератор — это по сути другая форма записи итератора — в виде функции с ключевым словом yield вместо return . Каждый экземпляр генераторного итератора, можно понимать как функцию с состоянием. Так, если обычная функция начинается каждый раз заново, то генератор продолжает выполнение с инструкции, следующей после последнего возвращенного yield .
В приведенном выше примере после вызова yield выполнение генератора прерывается, а после очередного вызова next() — возобновляется с того же места. И так происходит до тех пор, пока не будет вызван return или не произойдет выход из генератора (что эквивалентно return None ). В обоих случаях генерируется уже известное нам исключение StopIteration . Это означает, что данный итератор больше итерироваться не будет, и нужно создавать новый.
Вложенные генераторы
Генераторы могут быть вложенными, то есть использовать внутри себя другие генераторы. Вот пример бесконечного генератора факториала, использующего внутри себя бесконечный генератор натуральных чисел:
Тут вложенный генератор просто используется внутри другого и снаружи никак не заметен. Но бывают случаи, когда нужно значения из вложенного генератора передавать наружу напрямую:
Тут все три генератора возвращают один и тот же результат: 1, 2, 3, 4, 5. Но делают они это по-разному:
counter — это обычный генератор натуральных чисел, который будет использоваться как вложенный;
counter2 — генератор-обертка, которая просто перебирает элементы counter() и просто ретранслирует значения наружу;
counter3 — это то же самое, что и counter2 , только с «синтаксическим сахаром» yield from, который эквивалентен использованию for-in + yield, как в counter2 .
Эквивалентен, да не эквивалентен. Есть у него одно отличие. Когда из некоего outer_gen вызывается yield from inner_gen , устанавливается поле outer_gen.gi_yieldfrom = inner_gen . Каждый вызов next(outer_gen) , если его поле gi_yieldfrom не равно None , будет в действительности вызывать next(outer_gen.gi_yieldfrom) . То есть в нашем случае next(inner_gen) . И это будет происходить до тех пор, пока внутренний генератор не завершится.
Таким образом, с помощью yield from генератор фактически делегирует дальнейшее выполнение программы своему внутреннему генератору, а сам на это время как бы самоустраняется. Более наглядно это может продемонстрировать следующим примером, который также выводит числа 1, 2, 3, 4, 5:
Переход к сопрограммам
На таких простых примерах пока что не имеет значения — использовать yield from или for-in + yield. Но когда мы будем применять генераторы как сопрограммы (корутины), нам уже без yield from не обойтись. Данная конструкция будет автоматически перенаправлять нас к последней запускавшейся внутренней сопрограмме, на каком бы уровне вложенности она ни находилась. Ниже простой тому пример.
Но прежде нужно рассмотреть еще одно нововведение. Изначально генераторы могли только возвращать данные наружу ( yield ), но не получать их извне. Чтобы устранить этот недостаток в генератор был введен метод send() , который работает следующим образом:
Метод send() передает свой аргумент выражению yield , так что переменная, которой оно присваивается, получает значение аргумента send() . После этого продолжается обычное выполнение, как если бы был вызван просто next() ( next() включен в send() ). В примере выше скрипт вернет такие значения:
1 (первый вызов next(gen) ),
2 (вызов gen.send(None) , который эквивалентен next(gen) ),
5 (т.к. x=4, полученный от gen.send(4), после чего выполняются: i=x (4), i += 1 (5), yield i (5)).
Остальные 6, 7 выведутся в завершающем цикле for i in gen: .
Так вот, если мы выполним то же самое с обертками counter2 и counter3 , то gen.send(4) для counter3 , где используется yield from , будет работать так же, как и для counter .
И это не удивительно, ведь send() будет фактически перенаправлен в counter через поле gi_yieldfrom . Во втором случае counter используется опосредованно и 4-ка до него не дойдет. Чтобы counter2 работал, как counter , его придется значительным образом переделывать. Поэтому, чтобы не морочить себе голову, и был введен синтаксический сахар yield from , который, как мы увидели, не просто синтаксический сахар, но вполне полноценное нововведение.
Фактически уже на этом этапе мы из генераторов получили настоящие корутины (сопрограммы). Цепочка вложенных вызовов yield from (благодаря полю gi_yieldfrom ) позволяет нам мгновенно выходить и возвращаться в корутину на любом уровне вложенности, а использование send() вместо next() дает возможность пересылать в нее данные (если это нужно, конечно).
Применение генераторов как сопрограмм нам пригодится в реализации асинхронности. Для настоящей асинхронности не хватает только цикла с переключением между сопрограммами. Для этого мы задействуем Selector. А как именно это будет происходить, будет рассказано дальше.