Использовать exec или избегать?

Пишу discord бота на питоне. Хотелось бы вынести каждую команду в отдельный файл. Единственное что я придумал - читать содержимое файла и использовать функцию exec. Использовать import не получится, так как в коде команд используются глобальные переменные.

Уместно ли такое решение, или есть варианты получше? Недаром ведь говорят "eval is evil" :)

(код подаю в сокращенном и обрезанном виде, оставил только элементы по сути вопроса)

bot=Bot()

@bot.command()
async def command1(ctx):
    await ctx.send("command1 invoked")

@bot.command()
async def command2(ctx):
    await ctx.send("bot is on %s guilds"%len(bot.guilds))

Ответы (3 шт):

Автор решения: Xander

Вариант №1, если хочется чтобы декораторы выглядели именно как декораторы

В отдельных файлах заворачиваете ваши функции так:

def make_command1(bot):

    @bot.command()
    async def command1(ctx):
        await ctx.send("command1 invoked")

В главном файле используете их так:

from command1_file import make_command1


bot = Bot()

command1 = make_command1(bot)

Вариант №2

В отдельных файлах пишите только функции без декораторов:

async def command1(ctx):
    await ctx.send("command1 invoked")

А в главном файле привязываете их к боту так:

from command1_file import command1


bot = Bot()

command1 = bot.command()(command1)

Вариант 3, модификация варианта 1. Если вам так уж хочется иметь доступ к глобальным переменным не передавая их явно.

Вот так в отдельном файле:

def make_command1():
    
    bot = globals()['bot']

    @bot.command()
    async def command1(ctx):
        await ctx.send("command1 invoked")

Вот так в главном:

from command1_file import make_command1


bot = Bot()

command1 = make_command1()

Но я крайне не рекомендую так делать, лучше все переменные передавать явно. В данном варианте вам самостоятельно придётся следить за тем, чтобы все нужные глобальные переменные были объявлены строго до строчек command1 = make_command1()

→ Ссылка
Автор решения: Krutoy 4el

Нашел оптимальный для меня вариант.

helper_lib.py

#импорты библиотек и объявления функций

bot_commands/command1.py

from helper_lib import *

@bot.command()
async def command1(ctx):
   await ctx.send("command1")

bot.py

import helper_lib

bot=Bot()

helper_lib.bot=bot
#другие глобальные переменные

from bot_commands import command1

Вроде получилось нормально, комментируйте если что.

→ Ссылка
Автор решения: Fixator10

Используйте Cogs: https://discordpy.readthedocs.io/en/stable/ext/commands/cogs.html

# mycog.py
from discord.ext import commands

class MyCog(commands.Cog):
    """My awesome cog"""  # будет показываться в !help MyCog

    def __init__(self, bot: commands.Bot):  
        # можно обойтись и без ссылки на объект бота в классе, 
        # но в таком случае объект бота придётся доставать через ctx.bot
        self.bot = bot

    @commands.command()
    async def cmd(ctx):
        await ctx.send("invoked cmd")
# main.py
from mycog import MyCog

bot = commands.Bot()
bot.add_cog(MyCog(bot))  # добавить "модуль"
# bot.remove_cog("MyCog")  # убрать "модуль"
→ Ссылка