Очень часто при написании определённого функционала, определённой цепочки действий, возникает необходимость как-то давать указания боту на что реагировать, а что пропускать мимо ушей. Что сохранять и как всё это совмещать.
Всех приветствую дорогие форумчане! Сегодня мы поговорим про FSM (Finite State Machine), про машину состояний. Что это и как ей управляться. Давайте начнём.
Представим ситуацию. Вам нужно сделать бота для онлайн покупок. Вот вы написали функционал каталога, пришло время сделать самое основное. Функцию заказа. Вот пользователь вводит название товара, вы его сохраняется в переменную (не дай бог глобальную), далее вы предлагаете пользователю ввести свой адрес. И тут на тебе. А как? Как сделать так, чтобы после того как пользователь ввёл свой адрес, выполнился определённый handler?
Тут к нам на помощь приходит FSM (Finite State Machine).
Как вы могли понять, данная модель помогает передвигаться в определенные функции по скрипту, а также сохранять данные определённого пользователя, у которого активно FSM состояние.
Чтобы начать работать с машиной состояний, для начала нам нужно инициализировать хранилище в dp (Dispatcher)
Отлично! Теперь мы можем записывать данные пользователя в оперативную память.
Теперь давайте поговорим про состояния. Перед тем как создать какое-либо состояние, нам нужно создать класс, где мы поочередно опишем все states чтобы потом без проблем переключаться между ними. Возьмём пример с регистрацией пользователя и вводом его адреса. Создадим класс UserState.
Тут мы импортируем StatesGroup и State.
После создания класса, мы можем приступать к самому интересному, к основному написанию скрипта. Так что же будет делать бот?
1. спрашивать пользователя его имя
2. записывать имя в переменную состояния
3. переключаться на следующее состояние
4. спрашивать пользователя его адрес
5. записывать адрес в переменную состояния
6. выводить полученную информацию о пользователе
7. очищать состояние пользователя, а также все данные
Для начала создадим handler, который будет обрабатывать команду /reg:
Тут у нас встречается подобная строчка
Мы обращаемся к классу UserState, далее к состоянию name, а методом set() мы устанавливаем данное состояние. Теперь у нас пользователь имеет состояние UserState.name и мы можем создать handler, который будет реагировать ТОЛЬКО на это состояние. Давайте же создадим такой handler в который пользователь будет записывать своё имя:
Итак, давайте разбираться:
Тут мы вторым аргументом передали state: FSMContext для того, чтобы мы могли записывать данные пользователя в память. Нам также необходимо импортировать:
Далее:
Здесь мы вызываем метод update_data(), который сохраняет данные в память, где единственным аргументом мы указываем название ключа в памяти 'username', а также его значение 'message.text'.
Далее просим пользователя ввести его адрес, а после, неявно переключаем состояние на следующее (а следующий state у нас 'adress') с помощью метода
Однако переключайте состояния таким способом только тогда, когда точно знаете какой state (в классе) идёт дальше.
Также можно переключать состояния явно:
Отлично:claps: Теперь создадим финальный хэндлер где будем получать адрес пользователя, а также выводить ему всю имеющуюся информацию.
В начале в принципе мы с вами уже всё разобрали, state, FSMContext, update_data. Но тут у нас появляется новый актер.
С помощью метода get_data() мы получаем все ранее записанные в состояние данные, и сохраняем всё в переменную data. Теперь мы можем получить данные состояний по ключу через переменную data. Что.. собственно мы и делаем в следующей строчке при выводе всей информации пользователю.
И в конце концов мы всё завершаем методом finish()
Данный метод очищает все состояния пользователя, а также удаляет все ранее сохраненные данные. Если же вам надо сбросить только состояние, воспользуйтесь:
Весь код:
Друзья, надеюсь всем всё понятно разъяснил, если понравилась статья, дайте об этом знать:sobaken:
На этом всё, всем удачи и светлого ума! До скорого.
Всех приветствую дорогие форумчане! Сегодня мы поговорим про FSM (Finite State Machine), про машину состояний. Что это и как ей управляться. Давайте начнём.

Представим ситуацию. Вам нужно сделать бота для онлайн покупок. Вот вы написали функционал каталога, пришло время сделать самое основное. Функцию заказа. Вот пользователь вводит название товара, вы его сохраняется в переменную (не дай бог глобальную), далее вы предлагаете пользователю ввести свой адрес. И тут на тебе. А как? Как сделать так, чтобы после того как пользователь ввёл свой адрес, выполнился определённый handler?
Тут к нам на помощь приходит FSM (Finite State Machine).
Как вы могли понять, данная модель помогает передвигаться в определенные функции по скрипту, а также сохранять данные определённого пользователя, у которого активно FSM состояние.
Чтобы начать работать с машиной состояний, для начала нам нужно инициализировать хранилище в dp (Dispatcher)
Python:
from aiogram.contrib.fsm_storage.memory import MemoryStorage
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage)
Теперь давайте поговорим про состояния. Перед тем как создать какое-либо состояние, нам нужно создать класс, где мы поочередно опишем все states чтобы потом без проблем переключаться между ними. Возьмём пример с регистрацией пользователя и вводом его адреса. Создадим класс UserState.
Python:
from aiogram.dispatcher.filters.state import StatesGroup, State
class UserState(StatesGroup):
name = State()
address = State()
После создания класса, мы можем приступать к самому интересному, к основному написанию скрипта. Так что же будет делать бот?
1. спрашивать пользователя его имя
2. записывать имя в переменную состояния
3. переключаться на следующее состояние
4. спрашивать пользователя его адрес
5. записывать адрес в переменную состояния
6. выводить полученную информацию о пользователе
7. очищать состояние пользователя, а также все данные
Для начала создадим handler, который будет обрабатывать команду /reg:
Python:
@dp.message_handler(commands=['reg'])
async def user_register(message: types.Message):
await message.answer("Введите своё имя")
await UserState.name.set()
await UserState.name.set()
Мы обращаемся к классу UserState, далее к состоянию name, а методом set() мы устанавливаем данное состояние. Теперь у нас пользователь имеет состояние UserState.name и мы можем создать handler, который будет реагировать ТОЛЬКО на это состояние. Давайте же создадим такой handler в который пользователь будет записывать своё имя:
Python:
@dp.message_handler(state=UserState.name)
async def get_username(message: types.Message, state: FSMContext):
await state.update_data(username=message.text)
await message.answer("Отлично! Теперь введите ваш адрес.")
await UserState.next() # либо же UserState.address.set()
Итак, давайте разбираться:
async def get_username(message: types.Message, state: FSMContext):
Тут мы вторым аргументом передали state: FSMContext для того, чтобы мы могли записывать данные пользователя в память. Нам также необходимо импортировать:
from aiogram.dispatcher import FSMContext
Далее:
await state.update_data(username=message.text)
Здесь мы вызываем метод update_data(), который сохраняет данные в память, где единственным аргументом мы указываем название ключа в памяти 'username', а также его значение 'message.text'.
Далее просим пользователя ввести его адрес, а после, неявно переключаем состояние на следующее (а следующий state у нас 'adress') с помощью метода
await UserState.next()
Однако переключайте состояния таким способом только тогда, когда точно знаете какой state (в классе) идёт дальше.
Также можно переключать состояния явно:
await
UserState.address.set()
Отлично:claps: Теперь создадим финальный хэндлер где будем получать адрес пользователя, а также выводить ему всю имеющуюся информацию.
Python:
@dp.message_handler(state=UserState.address)
async def get_address(message: types.Message, state: FSMContext):
await state.update_data(address=message.text)
data = await state.get_data()
await message.answer(f"Имя: {data['username']}\n"
f"Адрес: {data['address']}")
await state.finish()
data = await state.get_data()
С помощью метода get_data() мы получаем все ранее записанные в состояние данные, и сохраняем всё в переменную data. Теперь мы можем получить данные состояний по ключу через переменную data. Что.. собственно мы и делаем в следующей строчке при выводе всей информации пользователю.
Python:
data = await state.get_data()
await message.answer(f"Имя: {data['username']}\n"
f"Адрес: {data['address']}")
И в конце концов мы всё завершаем методом finish()
await state.finish()
Данный метод очищает все состояния пользователя, а также удаляет все ранее сохраненные данные. Если же вам надо сбросить только состояние, воспользуйтесь:
await state.reset_state(with_data=False)
Весь код:
Python:
from aiogram import types
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup
class UserState(StatesGroup):
name = State()
address = State()
@dp.message_handler(commands=['reg'])
async def user_register(message: types.Message):
await message.answer("Введите своё имя")
await UserState.name.set()
@dp.message_handler(state=UserState.name)
async def get_username(message: types.Message, state: FSMContext):
await state.update_data(username=message.text)
await message.answer("Отлично! Теперь введите ваш адрес.")
await UserState.next() # либо же UserState.adress.set()
@dp.message_handler(state=UserState.address)
async def get_address(message: types.Message, state: FSMContext):
await state.update_data(address=message.text)
data = await state.get_data()
await message.answer(f"Имя: {data['username']}\n"
f"Адрес: {data['address']}")
await state.finish()
Итог

Друзья, надеюсь всем всё понятно разъяснил, если понравилась статья, дайте об этом знать:sobaken:
На этом всё, всем удачи и светлого ума! До скорого.