Reputation: 47
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
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
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