通过子模块[discord.py]中的命令与后台任务进行交互

时间:2018-09-14 14:54:37

标签: python python-3.x python-import python-asyncio discord.py

我有一个使用discord.py的不可重写版本编写的Discord机器人,该机器人发送类似心跳的消息(以及其他信息)。我不知道我是否正确理解它,但是从测试中发现,我需要在async def heartbeat()文件中拥有main.py函数。

摘录自main.py(心跳按预期运行):

[...]
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位于同一目录中)。 / p>

摘录自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)。

现在我的问题。 如何在heartbeat()中使用异步main.py,但如何通过dev.py模块中的命令影响任务?

如果我做不到,那么将命令保留在dev.py中的可行替代方法是什么(该函数本身不需要保留在main.py中,但最好保留在该位置)?

(我已经搜索了很长时间,但找不到像我这样的问题或碰巧也对我有用的解决方案)

1 个答案:

答案 0 :(得分:1)

在嵌齿轮中执行后台任务的最简单方法是向嵌齿轮中添加一个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_readyadd_cog机器将根据其名称进行选择。