discord.py开发机器人报NameError: name 'ws' is not defined求解
Discord.py 机器人 ?abc 指令触发 NameError 问题排查
问题背景
使用Python的discord.py库开发Discord对话机器人,引入asyncio、datetime、discord、openpyxl依赖,预期实现三个核心功能:
- 机器人登录成功后输出状态提示、设置游玩状态
- 接收
?learn指令时,将用户输入的问答对写入memory.xlsx文件存储 - 接收
?abc指令时,从memory.xlsx中读取对应问题的答案发送到频道
代码中已经编写ws = wb.active的工作表赋值逻辑,但运行时触发?abc指令直接抛出异常。
原代码与报错信息
问题代码
import asyncio import datetime import discord import openpyxl client = discord.Client(intents=discord.Intents.default()) @client.event async def on_ready(): print("접속완료했다용!"+ client.user.name) print(client.user.id) await client.change_presence(activity=discord.Game(name='디스코드봇 대화', type=1)) @client.event async def on_message(message): if message.content.startswith("?learn"): global ws word = message.content.split(" ") word_ = message.content.replace("?learn ", "").replace(word[1], "") wb = openpyxl.load_workbook("memory.xlsx") ws = wb.active for i in range(1, 10): if ws["A" + str(i)].value == "." or None: ws["A" + str(i)].value = word[1] ws["B" + str(i)].value = word_ await message.channel.send(ws["A1"].value) break wb.save("memory.xlsx") @client.event async def on_message(message): if message.content.startswith("?abc"): global ws word = message.content.split(" ") wb = openpyxl.load_workbook("memory.xlsx") ws = wb.active for i in range(1, 10): if ws["A" + str(i)].value == word[1]: await message.channel.send(ws["B" + str(i)].value) break
报错栈
[30;1m2022-07-10 19:12:26[0m [31mERROR [0m [35mdiscord.client[0m Ignoring exception in on_message [31mTraceback (most recent call last): File "C:\Users\준환\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\client.py", line 456, in _run_event await coro(*args, **kwargs) File "c:\Users\준환\Desktop\봇창고\학습\학습테스트.py", line 46, in on_message if ws["A" + str(i)].value == word[1]: NameError: name 'ws' is not defined[0m
报错产生原因
- 事件重复注册覆盖:discord.py框架下同类型事件如果重复定义,后注册的监听器会完全覆盖先注册的。代码中写了两个被
@client.event装饰的on_message函数,第一个处理?learn指令的逻辑完全不会被执行。 - 缩进层级错误:第二个
on_message函数内部代码缩进不符合Python语法规范,遍历Excel行匹配答案的for循环被写到了全局作用域,模块加载时就会执行,此时从未运行过ws变量的赋值逻辑,直接触发未定义错误。 - 判断逻辑错误:原代码中
if ws["A" + str(i)].value == "." or None写法存在逻辑漏洞,该表达式恒为真,无法正确识别空单元格;同时写死遍历前10行的逻辑灵活性差,也没有处理指令参数缺失的边界场景。 - 全局变量滥用:将工作表对象
ws声明为全局变量跨作用域访问,很容易出现变量未初始化就被调用的异常。
修复方案
按照以下规则调整代码即可解决问题:
- 合并两个重复的
on_message监听器到同一个函数内,避免事件覆盖 - 统一修正代码缩进,确保所有指令判断、Excel读写逻辑都在对应作用域内
- 修正空值判断逻辑,替换写死的行遍历范围为实际工作表最大行,增加参数合法性校验
- 移除不必要的全局
ws变量,每次操作Excel时在对应分支内完成加载、读写、关闭全流程,避免作用域异常 - 增加忽略机器人自身消息的判断,避免机器人响应自己的消息触发死循环
修复后可运行的完整代码:
import asyncio import datetime import discord import openpyxl # 注意需要开启消息内容权限,否则无法读取用户发送的指令内容 intents = discord.Intents.default() intents.message_content = True client = discord.Client(intents=intents) @client.event async def on_ready(): print("접속완료했다용!"+ client.user.name) print(client.user.id) await client.change_presence(activity=discord.Game(name='디스코드봇 대화', type=1)) @client.event async def on_message(message): # 忽略机器人自身发送的消息,避免死循环 if message.author == client.user: return # 处理?learn学习指令 if message.content.startswith("?learn"): # 拆分参数时限制拆分次数,避免回答内容包含空格被截断 word = message.content.split(" ", 2) if len(word) < 3: await message.channel.send("사용법: ?learn [질문] [답변]") return question = word[1] answer = word[2] wb = openpyxl.load_workbook("memory.xlsx") ws = wb.active save_success = False # 遍历现有行查找空位置写入新问答 for i in range(1, ws.max_row + 2): cell_val = ws[f"A{i}"].value if cell_val == "." or cell_val is None: ws[f"A{i}"].value = question ws[f"B{i}"].value = answer save_success = True await message.channel.send(f"학습완료: {question} -> {answer}") break if save_success: wb.save("memory.xlsx") wb.close() # 处理?abc查询指令 if message.content.startswith("?abc"): word = message.content.split(" ", 1) if len(word) < 2: await message.channel.send("사용법: ?abc [질문]") return target_question = word[1] wb = openpyxl.load_workbook("memory.xlsx") ws = wb.active find_success = False # 遍历行匹配问题 for i in range(1, ws.max_row + 1): if ws[f"A{i}"].value == target_question: await message.channel.send(ws[f"B{i}"].value) find_success = True break if not find_success: await message.channel.send("해당 질문에 대한 답변을 찾지 못했습니다.") wb.close() # 替换为你自己的机器人Token client.run("YOUR_BOT_TOKEN")
注意:新版discord.py需要在开发者后台开启Message Content Intent,同时代码中配置对应intents,否则无法读取普通频道内的用户消息内容。
内容的提问来源于stack exchange,提问作者joonhwan1
相关产品推荐
相关产品推荐

