Python Discord Bot线程中发送DM触发AttributeError问题求助
问题
我开发了一个从GitHub获取数据的Python Discord Bot,为避免GitHub限制,设置了每周自动更新数据。当Bot无法连接GitHub完成更新时,希望通过Discord私信收到通知。
已实现更新失败检测逻辑,但添加私信功能时遇到问题:自动更新器运行在单独线程中,执行guild = await bot.fetch_guild(GUILD_ID)时触发AttributeError: 'NoneType' object has no attribute 'request'错误。通过Discord命令触发notify_on_no_respond函数时可正常运行。
完整错误信息
Exception in thread Thread-1 (start_updater): Traceback (most recent call last): File "C:\Users\[User]\AppData\Local\Programs\Python\Python310\lib\threading.py", line 1016, in _bootstrap_inner self.run() File "C:\Users\[User]\AppData\Local\Programs\Python\Python310\lib\threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "\\StationDS216\home\Projects\Python\Discord Bots\test lab\Bot_auto_updater.py", line 7, in start_updater Thread_Auto_Updater = auto_updater( File "\\StationDS216\home\Projects\Python\Discord Bots\test lab\Bot_auto_updater.py", line 22, in __init__ self.data['Bot'].problem_occured() File "\\StationDS216\home\Projects\Python\Discord Bots\test lab\Bot_main.py", line 70, in problem_occured asyncio.run(notify_on_no_respond()) File "C:\Users\[User]\AppData\Local\Programs\Python\Python310\lib\asyncio\runners.py", line 44, in run return loop.run_until_complete(main) File "C:\Users\[User]\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 646, in run_until_complete return future.result() File "\\StationDS216\home\Projects\Python\Discord Bots\test lab\Bot_main.py", line 74, in notify_on_no_respond guild = await bot.fetch_guild(GUILD_ID) File "C:\Users\[User]\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\client.py", line 1188, in fetch_guild data = await self.http.get_guild(guild_id) File "C:\Users\[User]\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\http.py", line 192, in request async with self.__session.request(method, url, **kwargs) as r: AttributeError: 'NoneType' object has no attribute 'request'
复现代码
Bot_Game_main.py
import Bot_Game_auto_updater import os import discord import threading import asyncio from discord.ext import commands from dotenv import load_dotenv load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') MY_ID = os.getenv('DISCORD_MY_ID') GUILD_ID = os.getenv('DISCORD_GUILD_ID') bot = commands.Bot(command_prefix='/') class maintainer(object): def __init__(self): pass def start_thread(self, Bot_Game): threading.Thread(target=Bot_Game_auto_updater.start_updater, args=(Bot_Game,), daemon=True).start() def problem_occured(self): asyncio.run(notify_on_no_respond()) async def notify_on_no_respond(): guild = await bot.fetch_guild(GUILD_ID) user = await guild.fetch_member(MY_ID) await user.send('a problem occured') Bot_Game = maintainer() Bot_Game.start_thread(Bot_Game) bot.run(TOKEN)
Bot_Game_auto_updater.py
def start_updater(Bot_Game): Thread_Auto_Updater = auto_updater(Bot_Game) class auto_updater(object): def __init__(self, Bot_Game): Bot_Game.problem_occured()
解决方法
错误核心原因有两点:
- 线程启动时机过早:在
bot.run(TOKEN)前启动更新线程,此时Bot未完成初始化,内部HTTP会话未创建,导致API调用失败。 - 跨线程异步调用方式错误:用
asyncio.run()会创建新事件循环,而discord.py的Bot实例绑定在自身启动的循环中,跨循环调用会导致会话无法访问。
以下是具体修复步骤:
1. 延迟线程启动,等待Bot初始化完成
通过discord.py的on_ready事件触发更新线程,确保Bot完成登录初始化:
修改Bot_Game_main.py的启动逻辑:
# 移除原有的Bot_Game.start_thread(Bot_Game) @bot.event async def on_ready(): print(f'Logged in as {bot.user}') # Bot初始化完成后启动更新线程 Bot_Game.start_thread(Bot_Game)
2. 正确在非异步线程中调用Bot异步方法
不要用asyncio.run(),而是利用Bot实例的loop属性,将异步任务提交到Bot所在的事件循环执行:
修改maintainer类的problem_occured方法:
def problem_occured(self): # 将异步任务提交到Bot的事件循环 asyncio.run_coroutine_threadsafe(notify_on_no_respond(), bot.loop)
3. 优化私信发送逻辑(可选)
无需先获取公会再取成员,直接用bot.fetch_user()获取用户,减少API调用:
async def notify_on_no_respond(): user = await bot.fetch_user(MY_ID) await user.send('更新出现问题,无法连接GitHub')
修复后的完整代码
Bot_Game_main.py
import Bot_Game_auto_updater import os import discord import threading import asyncio from discord.ext import commands from dotenv import load_dotenv load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') MY_ID = int(os.getenv('DISCORD_MY_ID')) # 转换为整数类型 GUILD_ID = os.getenv('DISCORD_GUILD_ID') bot = commands.Bot(command_prefix='/') class maintainer(object): def __init__(self): pass def start_thread(self, Bot_Game): threading.Thread(target=Bot_Game_auto_updater.start_updater, args=(Bot_Game,), daemon=True).start() def problem_occured(self): # 提交任务到Bot的事件循环 asyncio.run_coroutine_threadsafe(notify_on_no_respond(), bot.loop) async def notify_on_no_respond(): # 直接获取用户,无需经过公会 user = await bot.fetch_user(MY_ID) await user.send('更新出现问题,无法连接GitHub') Bot_Game = maintainer() @bot.event async def on_ready(): print(f'已登录为 {bot.user}') Bot_Game.start_thread(Bot_Game) bot.run(TOKEN)
Bot_Game_auto_updater.py
def start_updater(Bot_Game): Thread_Auto_Updater = auto_updater(Bot_Game) class auto_updater(object): def __init__(self, Bot_Game): # 模拟更新失败触发通知 Bot_Game.problem_occured()
内容的提问来源于stack exchange,提问作者aszubat
相关产品推荐
相关产品推荐

