You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

python-telegram-bot:如何依次提问并将用户答案存入列表?

问题描述

需求:

  • 通过InlineKeyboard展示问题,提供“TRUE”和“FALSE”选项按钮,收集用户对不同话题的反馈并存入number_list列表
  • 依次询问用户art、culture、sports等话题的喜好

当前问题:运行/start命令后第一个问题正常显示,但用户点击选项按钮后,代码中的循环会快速遍历所有话题,直接显示最后一个问题;后续每次点击按钮都会重复显示这个最后一个问题。

相关代码片段

list_button函数

async def list_button(update: Update, context: ContextTypes) -> None:
    """Parses the CallbackQuery and updates the message text."""
    query = update.callback_query
    await query.answer()
    # Get the data from the callback_data.
    # If you're using a type checker like MyPy, you'll have to use typing.cast
    # to make the checker get the expected type of the callback_data
    number, number_list = cast(Tuple[int, List[int]], query.data)
    # append the number to the list
    number_list.append(number)

    # List with categories
    data=['art','culture','sports']

    # Iterate categories each time when asking for reply
    for i in data:
        await query.edit_message_text(
        text="Do you like {}? Choose if True or False: So far..{}".format(i,number_list),
        reply_markup=build_keyboard(number_list),
        )

    # we can delete the data stored for the query, because we've replaced the buttons
    context.drop_callback_data(query)

start与build_keyboard函数

async def start(update: Update, context: ContextTypes) -> None:
    """Sends a message with 5 inline buttons attached."""
    number_list: List[int] = []
    await update.message.reply_text("Do you like nature? Choose if True or False:", reply_markup=build_keyboard(number_list))

def build_keyboard(current_list: List[int]) -> InlineKeyboardMarkup:
    """Helper function to build the next inline keyboard."""
    return InlineKeyboardMarkup.from_column(
        [InlineKeyboardButton(str(i), callback_data=(i, current_list)) for i in ('True', 'False')]
    )

完整代码

import logging
from typing import List, Tuple, cast
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import (
    Application,
    CallbackQueryHandler,
    CommandHandler,
    ContextTypes,
    InvalidCallbackData,
    PicklePersistence,
)


# Enable logging
logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)


async def start(update: Update, context: ContextTypes) -> None:
    """Sends a message with 5 inline buttons attached."""
    number_list: List[int] = []
    await update.message.reply_text("Do you like nature? Choose if True or False:", reply_markup=build_keyboard(number_list))

def build_keyboard(current_list: List[int]) -> InlineKeyboardMarkup:
    """Helper function to build the next inline keyboard."""
    return InlineKeyboardMarkup.from_column(
        [InlineKeyboardButton(str(i), callback_data=(i, current_list)) for i in ('True', 'False')]
    )

async def list_button(update: Update, context: ContextTypes) -> None:
    """Parses the CallbackQuery and updates the message text."""
    query = update.callback_query
    await query.answer()
    # Get the data from the callback_data.
    # If you're using a type checker like MyPy, you'll have to use typing.cast
    # to make the checker get the expected type of the callback_data
    number, number_list = cast(Tuple[int, List[int]], query.data)
    # append the number to the list
    number_list.append(number)
    
    # List with categories
    data=['art','culture','sports']
    
    # Iterate categories each time when asking for reply
    for i in data:
        await query.edit_message_text(
        text="Do you like {}? Choose if True or False: So far..{}".format(i,number_list),
        reply_markup=build_keyboard(number_list),
        )

    # we can delete the data stored for the query, because we've replaced the buttons
    context.drop_callback_data(query)


TOKEN = 'XY'

def main() -> None:
    """Run the bot."""
    # We use persistence to demonstrate how buttons can still work after the bot was restarted
    persistence = PicklePersistence(filepath="arbitrarycallbackdatabot")
    # Create the Application and pass it your bot's token.
    application = (
        Application.builder()
        .token(TOKEN)
        .persistence(persistence)
        .arbitrary_callback_data(True)
        .build()
    )

    application.add_handler(CommandHandler("start", start))
    application.add_handler(CallbackQueryHandler(list_button))

    # Run the bot until the user presses Ctrl-C
    application.run_polling(stop_signals=None) 

if __name__ == "__main__":
    main()
解决方案

问题根源:

  1. list_button函数中的循环会在每次回调时遍历所有话题,快速更新消息内容到最后一个话题,用户无法看到中间步骤
  2. 直接将number_list传入callback_data,依赖序列化传递状态容易出问题,且无法跟踪当前询问到哪个话题

修改思路:

  • 使用context.user_data存储用户的反馈列表和当前话题索引,持久化跟踪会话状态
  • 每次回调只处理当前话题的回复,然后切换到下一个话题,直到所有话题询问完成

修改后的完整代码:

import logging
from typing import List, cast
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import (
    Application,
    CallbackQueryHandler,
    CommandHandler,
    ContextTypes,
    PicklePersistence,
)


# Enable logging
logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)

# 定义所有要询问的话题,包括初始的nature
TOPICS = ['nature', 'art', 'culture', 'sports']

async def start(update: Update, context: ContextTypes) -> None:
    """初始化会话,发送第一个问题"""
    # 在用户数据中初始化状态:反馈列表、当前话题索引
    context.user_data['number_list'] = []
    context.user_data['current_topic_idx'] = 0
    
    current_topic = TOPICS[0]
    await update.message.reply_text(
        f"Do you like {current_topic}? Choose if True or False:",
        reply_markup=build_keyboard()
    )

def build_keyboard() -> InlineKeyboardMarkup:
    """构建选项按钮,不需要传递列表,状态存在user_data中"""
    return InlineKeyboardMarkup.from_column(
        [InlineKeyboardButton(option, callback_data=option) for option in ('True', 'False')]
    )

async def list_button(update: Update, context: ContextTypes) -> None:
    """处理按钮回调,更新状态并发送下一个问题"""
    query = update.callback_query
    await query.answer()
    
    # 获取用户选择的答案
    answer = query.data
    # 从用户数据中取出当前状态
    number_list = context.user_data['number_list']
    current_idx = context.user_data['current_topic_idx']
    
    # 将答案存入列表(这里可以根据需要转成int,比如True=1,False=0)
    number_list.append(1 if answer == 'True' else 0)
    # 更新当前话题索引
    current_idx += 1
    
    # 判断是否还有下一个话题
    if current_idx < len(TOPICS):
        next_topic = TOPICS[current_idx]
        # 更新用户数据中的索引
        context.user_data['current_topic_idx'] = current_idx
        # 编辑消息为下一个问题
        await query.edit_message_text(
            text=f"Do you like {next_topic}? Choose if True or False: So far..{number_list}",
            reply_markup=build_keyboard()
        )
    else:
        # 所有话题询问完成,显示最终结果
        await query.edit_message_text(
            text=f"所有问题已完成!你的反馈列表:{number_list}"
        )
        # 清除用户会话状态(可选)
        context.user_data.clear()

TOKEN = 'XY'

def main() -> None:
    """Run the bot."""
    persistence = PicklePersistence(filepath="arbitrarycallbackdatabot")
    application = (
        Application.builder()
        .token(TOKEN)
        .persistence(persistence)
        .arbitrary_callback_data(True)
        .build()
    )

    application.add_handler(CommandHandler("start", start))
    application.add_handler(CallbackQueryHandler(list_button))

    application.run_polling(stop_signals=None) 

if __name__ == "__main__":
    main()

修改说明:

  • 把所有话题统一放在TOPICS列表中,包括初始的nature,方便统一管理
  • 使用context.user_data存储用户的number_list和current_topic_idx,确保会话状态正确跟踪
  • build_keyboard不再需要传递列表,按钮的callback_data只传递选项本身
  • list_button函数中去掉循环,每次只处理当前回复,然后判断是否还有下一个话题,逐步推进询问流程
  • 当所有话题询问完成后,显示最终的反馈列表并清除会话状态

内容的提问来源于stack exchange,提问作者newbie_coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:30:40