【问题标题】:Interact with background task with commands in submodule [discord.py]使用子模块中的命令与后台任务交互 [discord.py]
【发布时间】:2019-02-19 09:57:21
【问题描述】:

我有一个用 non-rewrite 版本的 discord.py 编写的 Discord 机器人,它发送类似心跳的消息(除其他外)。不知道我理解对不对,但是通过测试发现我需要在main.py文件中有async def heartbeat()函数。

摘自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 位于同一目录中)。

摘自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 中,但最好保留在那里)?

(我已经搜索了很长时间,但找不到像我这样的问题或恰好也适合我的解决方案)

【问题讨论】:

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


    【解决方案1】:

    在 cog 中拥有后台任务的最简单方法是将 on_ready 协程添加到将启动后台任务的 cog,而不是手动启动它:

    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))
    

    请注意,您不需要在 cog 中使用任何东西来装饰 on_readyadd_cog 机器会根据其名称来选择它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多