5
\$\begingroup\$

So I am working on writing a discord bot with discord.py.

I decided, rather than making a serie of if/elif, to map the messages to the functions in a dictionnary, like so:

my_dict = { "!trigger": func }

I then wanted to parse this dictionnary into an async for loop, but ran into some problems. To face those problems, I decided to write a dict() subclass, which I called AsyncDict. I just want to know if this is safe to work like this, or not? The main purpose of this little class is to avoid errors like "an async for loop needs an item that defines the function __aiter__"

AsyncDict class:

class AsyncDict(dict):
    def __init__(self, *args, **kwargs):
        super(AsyncDict, self).__init__(*args, **kwargs)

    async def items(self):
        items = super().items()
        for item in items:
            yield item

    async def keys(self):
        keys = super().keys()
        for key in keys:
            yield key

    async def values(self):
        values = super().values()
        for value in values:
            yield 

Once this is done, I just create my dictionary "as usual": calls = AsyncDict([("!ping", ping)])

Here is the relevant code in my main.py:

import discord
import asyncio

async def ping(message):
    await client.send_message(message.channel, 'pong')

calls = AsyncDict([("!ping", ping)])

@client.event
async def on_message(message):
    async for k, v in calls.items():
        if message.content.startswith(k):
            await v(message)

I just want to know if it's "safe" to work like this (cf.: AsyncDict)? I'm quite new to asynchronous development and I don't know if I'm missing something.

\$\endgroup\$
3
\$\begingroup\$

I saw your post on python-ideas. I think you have a misconception about native coroutines (i.e. async def). For example, this method in AsyncDict:

async def items(self):
    items = super().items()
    for item in items:
        yield item

This method does not have any await expressions, therefore the method itself should not be async def. It should just be a regular def. You may not realize it, but an async def function can still call regular def functions, and it can use regular with and for blocks.

In your use case, you should use a regular dictionary and a regular for loop:

calls = {"!ping": ping}

@client.event
async def on_message(message):
    for k, v in calls.items():
        if message.content.startswith(k):
            await v(message)

This is perfectly legal, and in fact better than trying to create "Async" variants of common data structures that don't need it.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer. What I wanted to do was some kind of iterable on which you could be able to do some actions on all elements at the same time. Which is, like you just told me, some misconception about how asyncio works. But if this is not the way to achieve this, how could I do so? I was just assuming that my dict would be taking more and more keys and so the for k, v in calls.items() would be some blocking factor that would sort of slow down my bot's answer on the discord channel \$\endgroup\$ – Yoiro Aug 21 '18 at 8:43
  • 1
    \$\begingroup\$ Asyncio offers concurrency, but not parallelism. So it is not possible, "to do some actions all elements at the same time" with asyncio. (See this question.) If you really want parallelism, you'll need to use threading or multiprocessing, but for your Discord bot you should not worry about optimization until you've got it working and can measure how fast it runs. In practice, iterating over a small dictionary should not be a bottleneck for your bot. \$\endgroup\$ – Mark E. Haase Aug 21 '18 at 14:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.