BenjeminStar
BenjeminStar

Reputation: 47

Discord py create a new tasks.loop object for each loop

I am trying to run a task.loop multiple times with a Discord bot, however this is not working as the loop is already running.

I found this answer in another question:

async def loop(name):
    print(name)

names = ["Jon", "Joseph"]

loops = {name: tasks.loop(seconds=10)(name) for name in names}

However, I can't figure it out. I don't understand how to automatically create a new loop from this, I always get this error with this program:

Traceback (most recent call last):
  File "main.py", line 11, in <module>
    loops = {name: tasks.loop(seconds=10)(name) for name in names}
  File "main.py", line 11, in <dictcomp>
    loops = {name: tasks.loop(seconds=10)(name) for name in names}
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/tasks/__init__.py", line 506, in decorator
    return Loop(func, **kwargs)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/tasks/__init__.py", line 76, in __init__
    raise TypeError('Expected coroutine function, not {0.__name__!r}.'.format(type(self.coro)))
TypeError: Expected coroutine function, not 'str'.

I copied the code to try it out and pasted it into a new program, this now looks like this:

import discord
from discord.ext import tasks

async def loop(name):
    print(name)

names = ["Jon", "Joseph"]

loops = {name: tasks.loop(seconds=10)(name) for name in names}#What exactly does this line do?

What do I have to change so that this error goes away and the loop can be executed multiple times?

Upvotes: 1

Views: 1240

Answers (2)

Łukasz Kwieciński
Łukasz Kwieciński

Reputation: 15689

It's kinda hard to explain if you don't know the basics of decorators, take a look at this gigantic answer it will hopefully clear up some things for you.

The shortest explanation that I can give is that decorators wrap your function in another function that returns a function. What you're currently doing is wrapping a STRING instead of a function with the decorator.

async def loop(name):
    print(name)

names = ["Jon", "Joseph"]
                                 # `loop` is the coroutine we defined above
loops = {name: tasks.loop(seconds=10)(loop) for name in names}
# Note that this is just a dict that holds functions, to start it
for name, coro in loops.items():
    coro.start(name)

If you want to start the loops directly in the dict comprehension

loops = {name: tasks.loop(seconds=10)(loop).start(name) for name in names}

I'm unsure why would you want to do such thing if you can simply do:

names = ["Jon", "Joseph"]

@tasks.loop(seconds=10)
async def loop():
    for name in names:
        print(name)


loop.start()

Upvotes: 2

BenjeminStar
BenjeminStar

Reputation: 47

@Łukasz Kwieciński - I have to answer you like this because the answer is too long

It is only an example, I have defined a loop in my main script that should be called multiple times:

@tasks.loop(minutes=1)
async def AutoRole(ctx, guild:discord.guild, role:discord.Role, members:discord.Member, startTime:int, endTime:int):
  if (datetime.datetime.now().hour+1 >= startTime) and (datetime.datetime.now().hour+1 < endTime):
    for memberID in members:
      member = guild.get_member(memberID)
      await member.add_roles(role)
  else:
    for memberID in members:
      member = guild.get_member(memberID)
      await member.remove_roles(role)

I call this one like this:

@bot.command()
async def startAR(ctx, role:discord.Role)
  if ctx.message.author.guild_permissions.administrator:
    startTime = X #I retrieve this data from a database
    endTime = Y #I retrieve this data from a database
    members = Z #I retrieve this data from a database
    AutoRole.start(ctx, ctx.guild, role, members, startTime, endTime)
    echo.info(ctx, "Startet AutoRole " + role.name)
  else:
    await echo.warning(ctx, "You need Admin Role to edit roles")

Since you can create multiple AutoRoles, this loop must be called again, and different values will be supplied.

But for the Test your Answer works fine and it helps me:

async def loop(name):
    print(name)

names = ["Jon", "Joseph"]
                                 # `loop` is the coroutine we defined above
loops = {name: tasks.loop(seconds=10)(loop) for name in names}
# Note that this is just a dict that holds functions, to start it
for name, coro in loops.items():
    coro.start(name)

Upvotes: 0

Related Questions