FSM (Finite State Machine) — это математическая модель, используемая в программировании для управления последовательностью действий, где система может находиться только в
одном конкретном состоянии в каждый момент времени. В контексте Telegram-ботов FSM помогает организовать диалог с пользователем, разделяя его на логические этапы (состояния).
Основные компоненты FSM
Компонент |
Описание |
Состояния (States) |
Этапы диалога (например, "выбор жанра", "указание года"). |
Переходы (Transitions) |
Правила перехода между состояниями (например, после ответа о жанре → запрос года). |
События (Events) |
Действия пользователя (текст, команды), которые запускают переходы. |
Данные (Data) |
Временное хранилище ответов (например, context.user_data ). |
Как FSM работает в Telegram-боте?
Пример: опрос для рекомендации фильмов
-
Старт: Бот спрашивает жанр → состояние GENRE
.
-
Пользователь отвечает → бот переходит в состояние YEAR
и спрашивает год.
-
Пользователь указывает год → бот переходит в состояние RATING
и запрашивает рейтинг.
-
После всех ответов → бот анализирует данные и выдаёт результат.
Зачем использовать FSM?
-
Управление сложными диалогами
Без FSM код превращается в хаотичные if-else
, которые трудно поддерживать.
Пример плохого подхода:
if "жанр" in query:
await ask_year()
elif "год" in query:
await ask_rating()
-
Сохранение контекста
Данные (жанр, год) хранятся в context.user_data
между этапами.
-
Гибкость
Можно добавить новые состояния (например, выбор страны или актёра).
Реализация FSM в python-telegram-bot
1. Определение состояний
Каждому этапу присваивается уникальный числовой идентификатор:
from telegram.ext import ConversationHandler
# Этапы опроса
GENRE, YEAR, RATING = range(3)
2. Обработчики для каждого состояния
Каждая функция возвращает следующее состояние или ConversationHandler.END
для завершения.
async def ask_genre(update: Update, context: CallbackContext) -> int:
await update.message.reply_text("Укажите жанр:")
return YEAR # Переход к запросу года
async def ask_year(update: Update, context: CallbackContext) -> int:
context.user_data['genre'] = update.message.text # Сохраняем жанр
await update.message.reply_text("Укажите год:")
return RATING # Переход к запросу рейтинга
3. Настройка ConversationHandler
Связывает состояния и обработчики:
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)], # Точка входа
states={
GENRE: [MessageHandler(filters.TEXT, ask_genre)],
YEAR: [MessageHandler(filters.TEXT, ask_year)],
RATING: [MessageHandler(filters.TEXT, ask_rating)]
},
fallbacks=[CommandHandler('cancel', cancel)] # Отмена опроса
)
Пример: Полный код бота с FSM
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ConversationHandler, CallbackContext
# Состояния
GENRE, YEAR = range(2)
async def start(update: Update, context: CallbackContext) -> int:
await update.message.reply_text("🎬 Назовите жанр фильма:")
return GENRE
async def ask_genre(update: Update, context: CallbackContext) -> int:
context.user_data['genre'] = update.message.text
await update.message.reply_text("📅 Теперь укажите год:")
return YEAR
async def ask_year(update: Update, context: CallbackContext) -> int:
context.user_data['year'] = update.message.text
genre = context.user_data['genre']
year = context.user_data['year']
await update.message.reply_text(f"✅ Вы выбрали: {genre} ({year})")
return ConversationHandler.END # Завершаем диалог
async def cancel(update: Update, context: CallbackContext) -> int:
await update.message.reply_text("Опрос отменён.")
return ConversationHandler.END
def main():
application = Application.builder().token("TOKEN").build()
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
GENRE: [MessageHandler(filters.TEXT, ask_genre)],
YEAR: [MessageHandler(filters.TEXT, ask_year)]
},
fallbacks=[CommandHandler('cancel', cancel)]
)
application.add_handler(conv_handler)
application.run_polling()
if __name__ == "__main__":
main()