如何在 discord.py 中动态创建模块化选择菜单


本文介绍如何基于字典数据动态生成 discord 选择菜单(select menu),实现无需为每个问题重复定义 view 类的模块化设计,支持随机抽题、自动绑定选项与回调逻辑,并可链式推进多轮问答。

在使用 discord.py 构建交互式问答系统(如分院帽式性格测试)时,若将题目与选项硬编码进多个 View 类中,会导致代码冗余、难以维护。理想方案是:仅用一个可复用的 View 类,通过传入不同题目字典动态渲染菜单。以下是经过验证的专业级实现方式。

✅ 正确做法:运行时构建 View 类 + 装饰器绑定回调

核心思路是——在函数作用域内动态定义 View 子类,并用 @discord.ui.select 装饰其方法。这样每次调用都能生成一个携带当前题目上下文的新 View 实例,避免闭包陷阱和异步回调绑定错误。

import random
import discord
from discord import ui, Interaction

# 示例题库(可扩展为 Q1, Q2, ..., Q8)
Q1 = {
    "Aube ou crépuscule": {
        "Aube": {"gry": 73, "serd": 73, "pouf": 30, "serp": 26},
        "Crépuscule": {"gry": 27, "serd": 27, "pouf": 70, "serp": 74}
    },
    "Forêt ou rivière": {
        "Forêt": {"gry": 74, "serd": 73, "pouf": 26, "serp": 28},
        "Rivière": {"gry": 26, "serd": 27, "pouf": 74, "serp": 72}
    }
}

# 主要问答函数(支持递归/链式调用)
async def questionnaire_choixpeau(
    interaction: Interaction, 
    question_dict: dict, 
    user_id: int,
    is_first: bool = True
):
    # 1️⃣ 随机抽取一道题
    question = random.choice(list(question_dict.keys()))

    # 2️⃣ 动态构建 SelectOption 列表
    options = [
        discord.SelectOption(label=label) 
        for label in question_dict[question].keys()
    ]

    # 3️⃣ 在函数内定义 View 类(关键!确保每次调用都新建类,避免状态污染)
    class QuestionView(ui.View):
        def __init__(self):
            super().__init__(timeout=300)  # 5分钟超时

        @ui.select(
            placeholder=question,
            min_values=1,
            max_values=1,
            options=options
        )
        async def select_callback(self, ctx: Interaction, select: ui.Select):
            # 解析用户选择并更新数据库(示例)
            selected_label = select.values[0]
            scores = question_dict[question][selected_label]

            # ⚠️ 注意:此处需替换为你的真实数据库操作
            # cursor.execute("UPDATE ...", (*scores.values(), user_id))

            # 4️⃣ 链式调用下一道题(示例逻辑)
            next_dict = get_next_question_dict(question_dict)  # 请自行实现映射逻辑
            if next_dict:
                await ctx.response.defer()  # 防止响应超时
                await questionnaire_choixpeau(ctx, next_dict, user_id, is_first=False)
            else:
                await ctx.response.edit_message(
                    content="✅ 问卷已完成!正在计算分院结果...",
                    embed=None,
                    view=None
                )
                # await attribution_maison(ctx, user_id)

    # 5️⃣ 发送消息(首次用 send_message,后续用 edit_message)
    embed = discord.Embed(title="? 请选择一个答案", description=f"**{question}**")
    for opt in options:
        embed.add_field(name="", value=f"• {opt.label}", inline=False)

    if is_first:
        await interaction.response.send_message(
            embed=embed, 
            view=QuestionView(), 
            ephemeral=True
        )
    else:
        await interaction.response.edit_message(embed=embed, view=QuestionView())

# 辅助函数:返回下一题字典(可根据实际需求改为列表索引或字典映射)
def get_next_question_dict(current: dict) -> dict | None:
    # 示例:按 Q1→Q2→...→Q8 顺序推进
    mapping = {Q1: Q2, Q2: Q3, Q3: Q4, Q4: Q5, Q5: Q6, Q6: Q7, Q7: Q8}
    return mapping.get(current)

⚠️ 关键注意事项

  • 不要在 __init__ 中手动赋值 callback:select.callback = ... 会传入协程对象而非可调用函数,引发 TypeError: 'coroutine' object is not callable。
  • 禁止在类外定义 @ui.select 方法:装饰器必须作用于 View 子类内部方法,否则无法注册事件处理器。
  • 动态 View 类必须每次新建:若复用同一类,@ui.select 的 placeholder 和 options 将被缓存为首次调用值,导致后续题目显示错误。
  • 响应方式要区分首次与后续:首次用 interaction.response.send_message();后续必须用 interaction.response.edit_message()(因原消息已存在)。
  • 务必设置 timeout 并处理超时:长时间未响应的 View 应自动禁用,避免用户误点旧菜单。

✅ 总结

该方案实现了真正意义上的模块化、可复用、上下文感知的选择菜单系统
? 题目数据与 UI 逻辑完全解耦;
? 支持任意层级的链式问答流程;
? 无硬编码 View 类,降低维护成本;
? 兼容 ephemeral 模式,保障用户隐私。

只需将你的题库组织为嵌套字典,并实现 get_next_question_dict() 映射逻辑,即可快速部署完整分院测试系统。


# 处理器  # 编码  # app  # ai  # 作用域  # Object  # 子类  # select  # 闭包  # 对象  # 事件  # 异步  # ui  # 链式  # 首次  # 绑定  # 回调  # 复用  # 递归  # 多个  # 都能  # 只需 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化76771 】 【 技术知识130152 】 【 IDC云计算60162 】 【 营销推广131313 】 【 AI优化88182 】 【 百度推广37138 】 【 网站推荐60173 】 【 精选阅读31334


相关推荐: Win11怎么更改任务栏位置_修改注册表将Win11任务栏置顶【教程】  Win11关机界面怎么改_Win11自定义关机画面设置【工具】  Mac电脑如何恢复出厂设置_Mac抹掉数据并重装系统【安全指南】  MySQL 中使用 IF 和 CASE 实现查询字段的条件映射  Windows系统被恶意软件破坏后的恢复策略_错误提示修复方式  Mac如何修复应用程序权限问题_Mac磁盘工具修复权限【教程】  windows如何修改文件默认打开方式_windows设置程序关联教程  Win11怎么关闭OneDrive同步_Win11取消自动备份文件【教程】  Golang如何遍历目录文件_Golang filepath.Walk目录遍历操作方法  Linux如何使用grep搜索文件内容_Linux下正则表达式匹配与查找技巧【指南】  如何在Golang中解压文件_Golang compress/gzip解压操作方法  Win10怎样卸载DockerDesktop_Win10卸载DockerDesktop步骤【步骤】  VSC怎样用终端运行PHP_命令行执行脚本的步骤【教程】  Win11怎么关闭自动调节亮度 Win11禁用内容自适应亮度【设置】  如何使用Golang实现聊天室消息存档_存储聊天记录到文件  静态属性修改会影响所有实例吗_php作用域操作符下静态存储【教程】  Windows如何拦截2345弹窗广告_Windows拦截2345弹窗方法【步骤】  Mac怎么安装软件_Mac安装dmg与pkg文件的区别【指南】  如何在 Go 中正确反序列化多个并列的 XML 元素(而非 XML 数组)  如何使用Golang实现负载均衡_分发请求到多个服务节点  Win11怎么调整屏幕亮度_Windows 11调节显示器亮度护眼设置【步骤】  如何在Golang中实现邮件发送功能_Golang SMTP发送与错误处理示例  c++ reinterpret_cast怎么用 c++最危险的类型转换【详解】  Windows11怎么用“记事本”自动换行与编码 Windows11记事本启用自动换行选择UTF-8编码避免乱码兼容多语言【教程】  如何在 Go 开发中正确处理本地包导入与远程模块路径的一致性问题  如何在 Go 后端安全获取并验证前端存储的 JWT?  Win11文件扩展名怎么显示_Win11查看文件后缀名设置【基础】  如何用正则表达式精确匹配最多含一个换行符的起止片段  如何使用Golang reflect检查方法数量_动态分析类型方法  PythonWeb前后端整合项目教程_FastAPIReact完整实例  Windows 10自带杀毒软件在哪_Windows 10打开和使用Windows安全中心  如何在Golang中写入JSON文件_保存结构体数据到文件  PHP 中 require() 语句返回值的用法详解  Windows蓝屏BAD_POOL_HEADER故障详解_蓝屏池损坏错误修复指南  Python邮件系统自动化教程_批量发送解析与模板应用  如何使用Golang benchmark测量函数延迟_统计执行耗时  php查询数据怎么分组_groupby分组查询配合聚合函数【技巧】  如何解决同一段404代码在不同主机上表现不一致的问题  Win11怎么设置默认邮件客户端 Win11修改Mail应用关联【教程】  如何使用Golang log记录不同级别日志_Golang log Println与Fatal示例  如何在Golang中引入测试模块_Golang测试包导入与使用实践  Windows如何查看和管理已安装的字体?(字体文件夹)  php485返回空数组怎么回事_php485数据接收为空排查指南【详解】  如何在 Go 中创建包含 map 的 slice(嵌套数据结构)  如何在Golang中处理二进制数据_Golang io与encoding/binary二进制操作方法  Win11怎么关闭任务栏小组件_Windows11隐藏任务栏天气图标  Win10如何更改任务栏高度_Windows10解锁任务栏调整大小  零基础学会Python自动化办公_高效处理Excel与PDF文档  Django 密码修改后会话失效的解决方案  微信里的php文件怎么变mp4_微信接收php转mp4操作步骤【操作】 

 2026-01-05

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

致胜网络推广营销网


致胜网络推广营销网

致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 915688610

 17370845950

 915688610@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.