Why doesn't multiple on_message events work?

2019-01-12 12:41发布

问题:

Why can't I have multiple on_message events?

import discord

client = discord.Client()

@client.event
async def on_ready():
    print('in on_ready')

@client.event
async def on_message(message):
    print("in on_message #1")

@client.event
async def on_message(message):
    print("in on_message #2")

@client.event
async def on_message(message):
    print("in on_message #3")

client.run("TOKEN")

For example, if I typed anything in discord, it's always only the last on_message that gets triggered. How can I get all three to work?

回答1:

It's not possible with the native Client

You can only have one on_message, if you have multiple, only the last one will be called for the on_message event. You'll just need to combine your three on_message.

import discord

client = discord.Client()

@client.event
async def on_message(message):
    print("in on_message #1")
    print("in on_message #2")
    print("in on_message #3")

client.run("TOKEN")

Like any Python variable/function (unless the decorator stores your function, @client.event does it by keeping only the most recent callback), if multiple names are the same, the most recently will be kept, and all others will get overwritten.

This is a simple example I wrote to give you a broad understanding of how events in discord.py work (note: the actual code isn't exactly like this, as it's rewritten and significantly reduced).

class Client:
    def event(self, func):               
        if func.__name__ == "on_message":
            self.on_message_handle = func
            return func

    def receive_message(self, msg):
        func = getattr(self, "on_message_handle", None)
        if func is not None:
            func(msg)
        else:
            self.process_commands(msg)

client = Client()

@client.event
def on_message(msg):
    print("in on_message #1")

@client.event
def on_message(msg):
    print("in on_message #2")

client.receive_message("hello")
# "in on_message #2"

As you can see client.event only keep one instance of on_message.


You can with Bot instances

Alternatively, if you're using the ext.commands extension of discord.py, there is a native way to have multiple on_message callbacks. You do so by using defining them as a listener. You can have at most one on_message event, and infinite amounts of on_message listeners.

from discord.ext import commands

bot = commands.Bot('.')

@bot.event
async def on_message(msg):
    print("in on_message #1")
    await bot.process_commands(msg)  # so `Command` instances will still get called


@bot.listen()
async def on_message(msg):
    print("in on_message #2")


@bot.listen()
async def on_message(msg):
    print("in on_message #3")

bot.run("TOKEN")

When a message is received, all on_message #1-3 will all get printed.



回答2:

In python, functions are just objects.

>>> def foo():
...     print ("hi")

defines an object called foo, You can see this using a Python shell.

>>> foo
<function foo at 0x...>
>>> foo()
hi

If you define a new method after, or redefine the variable foo, you lose access to the initial function.

>>> foo = "hi"
>>> foo
hi
>>> foo()
Traceback ...:
    file "<stdin>" ...
TypeError: 'str' object is not callable

How the @client.event decorator works is it tells your client that new messages should be piped into the messages, and well, if the method gets redefined, it means the old method is lost.

>>> @bot.event
... async def on_message(m):
...     print(1)
...
>>> bot.on_message(None) # @bot.event makes the bot define it's own method
1
>>> @bot.event
... async def on_message(m):
...     print(2)
...
>>> bot.on_message(None) # bot's own method was redefined.
2