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

Python开发Discord机器人:动态生成按钮View失败排查

动态生成Discord按钮View的问题排查与修复

我在Python Discord机器人中可以正常定义如下discord.ui.View:

class ConfirmResetView(discord.ui.View):
    def __init__(self, cog):
        super().__init__(timeout=60)
        self.cog = cog  # 用于重置事件的cog引用
        self.answer = None  # 存储用户的选择

    @discord.ui.button(label="Yes", style=discord.ButtonStyle.blurple)
    async def yes_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        self.clear_items()
        self.answer = 'Yes'
        await interaction.response.edit_message(delete_after=.1)
        self.stop()

    @discord.ui.button(label="No", style=discord.ButtonStyle.red)
    async def no_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        self.clear_items()
        self.answer = 'No'
        await interaction.response.edit_message(delete_after=.1)
        self.stop()

并在命令中这样使用:

view = ConfirmResetView(self)
message = await ctx.send("Are you sure?", view=view) 
           
# 等待用户点击按钮
await view.wait()

print(view.answer)

我想要创建一个通用的View子类,用于生成带有任意数量按钮的交互提问,尝试了两种实现方式,但调用view = Question_dialog(self)时按钮并未显示,请问错误出在哪里?


第一种实现的问题

第一种实现代码:

import discord


class Question_dialog(discord.ui.View):
    def __init__(self, cog, timeout=60, buttons=[('Yes', 'Yes', discord.ButtonStyle.blurple), ('No', 'No', discord.ButtonStyle.red)]):
        super().__init__(timeout=timeout)
        self.cog = cog  # 用于重置事件的cog引用
        self.answer = None  # 存储用户的选择
        
        for i, (label, answer, style) in enumerate(buttons):
            @discord.ui.button(label=label, style=style)
            async def button(self, interaction: discord.Interaction, button: discord.ui.Button):
                self.clear_items()
                self.answer = answer
                await interaction.response.edit_message(delete_after=.1)
                self.stop()
        
            setattr(self, f"button{i}", button)

错误点:

  • @discord.ui.button装饰器生成的是类方法装饰器,必须绑定为类级别的方法,而不是实例属性。用setattr绑定到实例上的按钮不会被View识别。
  • 循环内的闭包存在变量捕获问题:所有按钮的answer都会指向循环最后一次的取值,即使按钮显示正常,点击后也会返回错误结果。

第二种实现的问题

第二种实现代码:

import discord

class QuestionDialog(discord.ui.View):
    def __init__(self, cog, timeout=60, buttons=[('Yes', 'Yes', discord.ButtonStyle.blurple), ('No', 'No', discord.ButtonStyle.red)]):
        super().__init__(timeout=timeout)
        self.cog = cog  # 用于重置事件的cog引用
        self.answer = None  # 存储用户的选择

        # 循环创建按钮
        for i, (label, answer, style) in enumerate(buttons):
            self.add_item(self.create_button(label, answer, style))

    def create_button(self, label, answer, style):
        # 辅助函数创建独立作用域的按钮
        async def button_callback(interaction: discord.Interaction):
            self.clear_items()
            self.answer = answer
            await interaction.response.edit_message(delete_after=.1)
            self.stop()

        # 创建并返回带回调的Button
        return discord.ui.button(label=label, style=style, custom_id=f"button_{label}", row=0, disabled=False)(button_callback)

错误点:

  • discord.ui.button装饰器的使用方式错误:该装饰器需要装饰协程函数来生成按钮,直接调用它返回的是装饰器函数,无法正确生成按钮实例。
  • 可变默认参数buttons=[...]会导致多次实例化时共享同一个列表,可能出现按钮重复或异常的情况。

修正后的通用实现

以下是修复后的代码,解决了所有问题:

import discord

class QuestionDialog(discord.ui.View):
    def __init__(self, cog, timeout=60, buttons=None):
        super().__init__(timeout=timeout)
        self.cog = cog
        self.answer = None
        # 避免可变默认参数的陷阱
        if buttons is None:
            buttons = [('Yes', 'Yes', discord.ButtonStyle.blurple), ('No', 'No', discord.ButtonStyle.red)]
        
        for label, answer, style in buttons:
            # 直接创建Button实例
            button = discord.ui.Button(label=label, style=style)
            
            # 通过默认参数绑定answer,解决闭包变量捕获问题
            async def button_callback(interaction: discord.Interaction, ans=answer):
                self.clear_items()
                self.answer = ans
                await interaction.response.edit_message(view=self, delete_after=0.1)
                self.stop()
            
            button.callback = button_callback
            self.add_item(button)

关键修复点:

  1. 规避可变默认参数:将buttons默认值设为None,初始化时再创建默认列表,防止多次实例化共享同一数据。
  2. 直接创建Button实例:不依赖装饰器,手动实例化discord.ui.Button并绑定回调函数。
  3. 解决闭包问题:将answer作为回调函数的默认参数传入,确保每个按钮捕获当前循环的对应值。
  4. 正确更新消息:编辑消息时传入view=self,确保按钮清除后同步更新到消息上。

使用方式

和原有用法完全一致:

view = QuestionDialog(self)
message = await ctx.send("Are you sure?", view=view) 
await view.wait()
print(view.answer)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:05:15