discord.py 中的异步竞争条件问题

Async race condition problem in discord.py

我想做一个简单的命令,允许用户给自己一个角色,但如果角色不存在,它会先创建角色,然后再给他们角色。这是我当前的代码:

@bot.command()
async def SetRole(ctx, arg):
    roles = ctx.guild.roles
    user = ctx.message.author

    #check if role does not exist
    if arg not in [role.name for role in roles]:
        await ctx.guild.create_role(name=arg)
    await user.add_roles(discord.utils.get(roles, name=arg))

当我 运行 $SetRole test_role 如果 test_role 不存在时的预期结果是机器人创建角色然后给我角色。但是,角色是创建的,但没有给我。这里有错误和堆栈跟踪:

Ignoring exception in command SetRole:
Traceback (most recent call last):
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 85, in wrapped
    ret = await coro(*args, **kwargs)
  File "main.py", line 17, in SetRole
    await user.add_roles(discord.utils.get(roles, name=arg))
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/member.py", line 777, in add_roles
    await req(guild_id, user_id, role.id, reason=reason)
AttributeError: 'NoneType' object has no attribute 'id'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/bot.py", line 939, in invoke
    await ctx.command.invoke(ctx)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 863, in invoke
    await injected(*ctx.args, **ctx.kwargs)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 94, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'id'

如果我 运行 第二次执行该命令,则角色已成功授予我。我认为发生的事情是出于某种原因,user.add_roles() 发生在 create_role 之前很可能是因为异步的一些奇怪之处。如何确保它在创建角色后添加角色?

这不是竞争条件,只是 discord.py 不会随处跟踪和改变对象

更好的实现方式是:

from typing import Union

@bot.command()
async def SetRole(ctx, role: Union[Role, str]):
    if isinstance(role, str):
        role = await ctx.guild.create_role(name=role)
    await user.add_roles(role)

Union 只是表示“任何给定类型”的一种方式,因此 discord.py [巧妙地] return 一个角色或一个字符串作为后备,然后我们可以通过检查 role 是否为字符串

来创建角色(如果角色尚不存在)