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()
解决方案
问题根源:
list_button函数中的循环会在每次回调时遍历所有话题,快速更新消息内容到最后一个话题,用户无法看到中间步骤- 直接将
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
相关产品推荐
相关产品推荐

