使用子模块 [discord.py] 中的命令与后台任务交互
Interact with background task with commands in submodule [discord.py]
我有一个用 非重写 版本 discord.py 编写的 Discord 机器人,它发送类似心跳的消息(以及其他信息)。我不知道我是否理解正确,但通过测试,我发现我需要在 main.py
文件中包含 async def heartbeat()
函数。
摘自 main.py
(heartbeat 按预期工作):
[...]
import asyncio
import datetime
from configparser import ConfigParser
startup_time = datetime.datetime.utcnow()
[...]
async def heartbeat():
await bot.wait_until_ready()
heartbeat_config = ConfigParser()
heartbeat_config.read('./config/config.ini')
hb_freq = int(heartbeat_config.get('Heartbeat', 'hb_freq')) # frequency of heartbeat message
hb_channel = heartbeat_config.get('Heartbeat', 'hb_channel') # target channel of heartbeat message
hb_channel = bot.get_channel(hb_channel) # get channel from bot's channels
await bot.send_message(hb_channel, "Starting up at: `" + str(startup_time) + "`")
await asyncio.sleep(hb_freq) # sleep for hb_freq seconds before entering loop
while not bot.is_closed:
now = datetime.datetime.utcnow() # time right now
tdelta = now - startup_time # time since startup
tdelta = tdelta - datetime.timedelta(microseconds=tdelta.microseconds) # remove microseconds from tdelta
beat = await bot.send_message(hb_channel, "Still running\nSince: `" + str(startup_time) + "`.\nCurrent uptime: `" + str(tdelta))
await asyncio.sleep(hb_freq) # sleep for hb_freq seconds before initialising next beat
await bot.delete_message(beat) # delete old beat so it can be replaced
[...]
if __name__ == "__main__":
global heartbeat_task
heartbeat_task = bot.loop.create_task(heartbeat()) # creates heartbeat task in the background
bot.run(token) # run bot
我有一些命令应该与创建的 heartbeat_task
交互,但它们在不同的模块中,称为 dev.py
(与 main.py
位于同一目录中) .
摘自dev.py
:
[...]
from main import heartbeat_task, heartbeat
[...]
@commands.group(pass_context=True)
async def heart(self, ctx):
if ctx.invoked_subcommand is None:
return
@heart.command(pass_context=True)
async def stop(self, ctx):
# should cancel the task from main.py
heartbeat_task.cancel()
await self.bot.say('Heartbeat stopped by user {}'.format(ctx.message.author.name))
@heart.command(pass_context=True)
async def start(self, ctx):
# start the heartbeat if it is not running
global heartbeat_task
if heartbeat_task.cancelled():
heartbeat_task = self.bot.loop.create_task(heartbeat())
await self.bot.say('Heartbeat started by user {}'.format(ctx.message.author.name))
else:
return
[...]
这些命令在作为 main.py
的一部分时工作得非常好(当然需要进行必要的调整,例如删除 self
、导入等),但是因为我想要所有与开发人员相关的命令进入他们自己的模块,所以我尝试移动它们。
我尝试加载模块时出现以下错误:
ImportError: cannot import name 'heartbeat_task'.
从 dev.py
中删除该导入会导致模块成功加载,但在使用任一命令时,控制台都会抛出错误:
NameError: name 'heartbeat_task' is not defined
可以追溯到行 heartbeat_task.cancel()
(在 heart stop
的情况下 // if heartbeat_task.cancelled():
(在 heart start
的情况下)。
现在我的问题。
我怎样才能在 main.py
中使用异步 heartbeat()
,但使用 dev.py
模块中的命令影响任务?
如果我不能,有什么可行的替代方法可以将命令保留在 dev.py
中(函数本身不需要保留在 main.py
中,但最好保留在那里)?
(我已经搜索了很长时间,但找不到像我这样的问题或恰好适合我的解决方案)
在 cog 中拥有后台任务的最简单方法是向 cog 添加一个 on_ready
协程来启动后台任务,而不是手动启动它:
class MyCog:
def __init__(self, bot):
self.bot = bot
async def heartbeat(self):
...
async def on_ready(self):
self.heartbeat_task = self.bot.loop.create_task(heartbeat())
@commands.command(pass_context=True)
async def stop(self, ctx):
self.heartbeat_task.cancel()
await self.bot.say('Heartbeat stopped by user {}'.format(ctx.message.author.name))
def setup(bot):
bot.add_cog(MyCog(bot))
请注意,您不需要用齿轮中的任何东西装饰 on_ready
,add_cog
机器会根据它的名称来拾取它。
我有一个用 非重写 版本 discord.py 编写的 Discord 机器人,它发送类似心跳的消息(以及其他信息)。我不知道我是否理解正确,但通过测试,我发现我需要在 main.py
文件中包含 async def heartbeat()
函数。
摘自 main.py
(heartbeat 按预期工作):
[...]
import asyncio
import datetime
from configparser import ConfigParser
startup_time = datetime.datetime.utcnow()
[...]
async def heartbeat():
await bot.wait_until_ready()
heartbeat_config = ConfigParser()
heartbeat_config.read('./config/config.ini')
hb_freq = int(heartbeat_config.get('Heartbeat', 'hb_freq')) # frequency of heartbeat message
hb_channel = heartbeat_config.get('Heartbeat', 'hb_channel') # target channel of heartbeat message
hb_channel = bot.get_channel(hb_channel) # get channel from bot's channels
await bot.send_message(hb_channel, "Starting up at: `" + str(startup_time) + "`")
await asyncio.sleep(hb_freq) # sleep for hb_freq seconds before entering loop
while not bot.is_closed:
now = datetime.datetime.utcnow() # time right now
tdelta = now - startup_time # time since startup
tdelta = tdelta - datetime.timedelta(microseconds=tdelta.microseconds) # remove microseconds from tdelta
beat = await bot.send_message(hb_channel, "Still running\nSince: `" + str(startup_time) + "`.\nCurrent uptime: `" + str(tdelta))
await asyncio.sleep(hb_freq) # sleep for hb_freq seconds before initialising next beat
await bot.delete_message(beat) # delete old beat so it can be replaced
[...]
if __name__ == "__main__":
global heartbeat_task
heartbeat_task = bot.loop.create_task(heartbeat()) # creates heartbeat task in the background
bot.run(token) # run bot
我有一些命令应该与创建的 heartbeat_task
交互,但它们在不同的模块中,称为 dev.py
(与 main.py
位于同一目录中) .
摘自dev.py
:
[...]
from main import heartbeat_task, heartbeat
[...]
@commands.group(pass_context=True)
async def heart(self, ctx):
if ctx.invoked_subcommand is None:
return
@heart.command(pass_context=True)
async def stop(self, ctx):
# should cancel the task from main.py
heartbeat_task.cancel()
await self.bot.say('Heartbeat stopped by user {}'.format(ctx.message.author.name))
@heart.command(pass_context=True)
async def start(self, ctx):
# start the heartbeat if it is not running
global heartbeat_task
if heartbeat_task.cancelled():
heartbeat_task = self.bot.loop.create_task(heartbeat())
await self.bot.say('Heartbeat started by user {}'.format(ctx.message.author.name))
else:
return
[...]
这些命令在作为 main.py
的一部分时工作得非常好(当然需要进行必要的调整,例如删除 self
、导入等),但是因为我想要所有与开发人员相关的命令进入他们自己的模块,所以我尝试移动它们。
我尝试加载模块时出现以下错误:
ImportError: cannot import name 'heartbeat_task'.
从 dev.py
中删除该导入会导致模块成功加载,但在使用任一命令时,控制台都会抛出错误:
NameError: name 'heartbeat_task' is not defined
可以追溯到行 heartbeat_task.cancel()
(在 heart stop
的情况下 // if heartbeat_task.cancelled():
(在 heart start
的情况下)。
现在我的问题。
我怎样才能在 main.py
中使用异步 heartbeat()
,但使用 dev.py
模块中的命令影响任务?
如果我不能,有什么可行的替代方法可以将命令保留在 dev.py
中(函数本身不需要保留在 main.py
中,但最好保留在那里)?
(我已经搜索了很长时间,但找不到像我这样的问题或恰好适合我的解决方案)
在 cog 中拥有后台任务的最简单方法是向 cog 添加一个 on_ready
协程来启动后台任务,而不是手动启动它:
class MyCog:
def __init__(self, bot):
self.bot = bot
async def heartbeat(self):
...
async def on_ready(self):
self.heartbeat_task = self.bot.loop.create_task(heartbeat())
@commands.command(pass_context=True)
async def stop(self, ctx):
self.heartbeat_task.cancel()
await self.bot.say('Heartbeat stopped by user {}'.format(ctx.message.author.name))
def setup(bot):
bot.add_cog(MyCog(bot))
请注意,您不需要用齿轮中的任何东西装饰 on_ready
,add_cog
机器会根据它的名称来拾取它。